From 66b743b6488a000bd5723f103d98ca418ec6970b Mon Sep 17 00:00:00 2001 From: Copilot <198982749+Copilot@users.noreply.github.com> Date: Sun, 17 May 2026 19:49:59 +0200 Subject: [PATCH 01/11] Migrate integration tests to in-process hosts via `WebApplicationFactory` and remove external server orchestration from build (#564) Co-authored-by: xperiandri <2365592+xperiandri@users.noreply.github.com> Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com> Co-authored-by: Andrii Chebukin --- FSharp.Data.GraphQL.Integration.slnx | 5 + Packages.props | 1 + build/Program.fs | 72 +- .../star-wars-fabulous-client/StarWars.slnx | 6 + .../StarWars/Common.fs | 5 +- .../StarWars/StarWars.fsproj | 20 +- ...Sharp.Data.GraphQL.IntegrationTests.fsproj | 17 +- .../IntrospectionUpdateTests.fs | 85 + .../LocalProviderTests.fs | 611 ++++-- ...ProviderWithOptionalParametersOnlyTests.fs | 607 ++++-- .../OperationErrorTests.fs | 10 +- .../ReservedScalarNameProviderTests.fs | 16 +- .../SwapiLocalProviderTests.fs | 145 +- .../SwapiRemoteProviderTests.fs | 167 +- .../TestHosts.fs | 41 + .../integration-introspection.json | 1929 +++++++++++++++++ .../introspection.json | 2 +- 17 files changed, 3111 insertions(+), 628 deletions(-) create mode 100644 tests/FSharp.Data.GraphQL.IntegrationTests/IntrospectionUpdateTests.fs create mode 100644 tests/FSharp.Data.GraphQL.IntegrationTests/TestHosts.fs create mode 100644 tests/FSharp.Data.GraphQL.IntegrationTests/integration-introspection.json diff --git a/FSharp.Data.GraphQL.Integration.slnx b/FSharp.Data.GraphQL.Integration.slnx index ebd077769..b8e081034 100644 --- a/FSharp.Data.GraphQL.Integration.slnx +++ b/FSharp.Data.GraphQL.Integration.slnx @@ -4,6 +4,9 @@ + + + @@ -12,7 +15,9 @@ + + diff --git a/Packages.props b/Packages.props index 7d3b9582b..323f603f2 100644 --- a/Packages.props +++ b/Packages.props @@ -76,6 +76,7 @@ + diff --git a/build/Program.fs b/build/Program.fs index 22bbb133c..f23d68415 100644 --- a/build/Program.fs +++ b/build/Program.fs @@ -2,8 +2,6 @@ module Program open System open System.IO -open System.Net.Http -open System.Text.Json open Fake.Core open Fake.Core.TargetOperators @@ -123,26 +121,6 @@ let runTests (project : string) (args : string) = |> _.WithCommon(DotNetCli.setVersion)) project -let starWarsServerStream = StreamRef.Empty - -let [] StartStarWarsServerTarget = "StartStarWarsServer" -Target.create StartStarWarsServerTarget <| fun _ -> - Target.activateFinal "StopStarWarsServer" - - let project = - "samples" - "star-wars-api" - "star-wars-api.fsproj" - - startGraphQLServer project 8086 starWarsServerStream - -let [] StopStarWarsServerTarget = "StopStarWarsServer" -Target.createFinal StopStarWarsServerTarget <| fun _ -> - try - starWarsServerStream.Value.Write ([| 0uy |], 0, 1) - with e -> - printfn "%s" e.Message - let integrationTestServerProjectPath = "tests" "FSharp.Data.GraphQL.IntegrationTests.Server" @@ -179,58 +157,35 @@ Target.createFinal StopIntegrationServerTarget <| fun _ -> with e -> printfn "%s" e.Message -let [] UpdateIntrospectionFileTarget = "UpdateIntrospectionFile" -Target.create UpdateIntrospectionFileTarget <| fun _ -> - let client = new HttpClient () - (task { - let! result = client.GetAsync ("http://localhost:8086") - let! contentStream = result.Content.ReadAsStreamAsync () - let! jsonDocument = JsonDocument.ParseAsync contentStream - let file = - new FileStream ("tests/FSharp.Data.GraphQL.IntegrationTests/introspection.json", FileMode.Create, FileAccess.Write, FileShare.None) - let encoder = System.Text.Encodings.Web.JavaScriptEncoder.UnsafeRelaxedJsonEscaping - let jsonWriterOptions = JsonWriterOptions (Indented = true, Encoder = encoder) - let writer = new Utf8JsonWriter (file, jsonWriterOptions) - jsonDocument.WriteTo writer - do! writer.FlushAsync () - do! writer.DisposeAsync () - do! file.DisposeAsync () - result.Dispose () - }) - .Wait () - client.Dispose () - -let unitTestsProjectPath = - "tests" - "FSharp.Data.GraphQL.Tests" - "FSharp.Data.GraphQL.Tests.fsproj" - let integrationTestsProjectPath = "tests" "FSharp.Data.GraphQL.IntegrationTests" "FSharp.Data.GraphQL.IntegrationTests.fsproj" -let [] BuildIntegrationTestsTarget = "BuildIntegrationTests" -Target.create BuildIntegrationTestsTarget <| fun _ -> +let [] UpdateIntrospectionFileTarget = "UpdateIntrospectionFile" +Target.create UpdateIntrospectionFileTarget <| fun _ -> integrationTestsProjectPath - |> DotNet.build (fun options -> { + |> DotNet.test (fun options -> { options with + Framework = Some DotNetMoniker Configuration = configuration + Common = { DotNetCli.setVersion options.Common with CustomParams = Some "--filter FullyQualifiedName~IntrospectionUpdateTests" } MSBuildParams = { options.MSBuildParams with DisableInternalBinLog = true + Verbosity = Some Normal } - Common = DotNetCli.setVersion options.Common }) +let unitTestsProjectPath = + "tests" + "FSharp.Data.GraphQL.Tests" + "FSharp.Data.GraphQL.Tests.fsproj" + let [] RunUnitTestsTarget = "RunUnitTests" Target.create RunUnitTestsTarget <| fun _ -> runTests unitTestsProjectPath "" -let [] RunIntegrationTestsTarget = "RunIntegrationTests" -Target.create RunIntegrationTestsTarget <| fun _ -> - runTests integrationTestsProjectPath "" //"--filter Execution=Sync" - let prepareDocGen () = Shell.rm "docs/release-notes.md" Shell.cp "RELEASE_NOTES.md" "docs/RELEASE_NOTES.md" @@ -406,12 +361,7 @@ Target.create "PackAndPush" ignore ==> RestoreTarget ==> BuildTarget ==> RunUnitTestsTarget -==> StartStarWarsServerTarget -==> BuildIntegrationTestServerTarget -==> StartIntegrationServerTarget ==> UpdateIntrospectionFileTarget -==> BuildIntegrationTestsTarget -==> RunIntegrationTestsTarget ==> "All" =?> (GenerateDocsTarget, Environment.environVar "GITHUB_ACTIONS" = "True") |> ignore diff --git a/samples/star-wars-fabulous-client/StarWars.slnx b/samples/star-wars-fabulous-client/StarWars.slnx index 51d68264a..52cff01ec 100644 --- a/samples/star-wars-fabulous-client/StarWars.slnx +++ b/samples/star-wars-fabulous-client/StarWars.slnx @@ -14,6 +14,12 @@ + + + + + + diff --git a/samples/star-wars-fabulous-client/StarWars/Common.fs b/samples/star-wars-fabulous-client/StarWars/Common.fs index 3974e6c92..1de5ae4e9 100644 --- a/samples/star-wars-fabulous-client/StarWars/Common.fs +++ b/samples/star-wars-fabulous-client/StarWars/Common.fs @@ -5,7 +5,10 @@ open FSharp.Data.GraphQL module Commands = - type GraphQLApi = GraphQLProvider<"http://localhost:8086"> + [] + let IntrospectionPath = "../../../tests/FSharp.Data.GraphQL.IntegrationTests/introspection.json" + + type GraphQLApi = GraphQLProvider let GetCharactersData = GraphQLApi.Operation<"queries/FetchCharacters.graphql">() type Character = GraphQLApi.Operations.FetchCharacters.Types.CharactersFields.Character diff --git a/samples/star-wars-fabulous-client/StarWars/StarWars.fsproj b/samples/star-wars-fabulous-client/StarWars/StarWars.fsproj index d972d6d59..4769849ad 100644 --- a/samples/star-wars-fabulous-client/StarWars/StarWars.fsproj +++ b/samples/star-wars-fabulous-client/StarWars/StarWars.fsproj @@ -4,7 +4,7 @@ false - + @@ -18,16 +18,16 @@ - - - - - - - - + + + + + + + + - \ No newline at end of file + diff --git a/tests/FSharp.Data.GraphQL.IntegrationTests/FSharp.Data.GraphQL.IntegrationTests.fsproj b/tests/FSharp.Data.GraphQL.IntegrationTests/FSharp.Data.GraphQL.IntegrationTests.fsproj index f133dee2a..5d7e2d004 100644 --- a/tests/FSharp.Data.GraphQL.IntegrationTests/FSharp.Data.GraphQL.IntegrationTests.fsproj +++ b/tests/FSharp.Data.GraphQL.IntegrationTests/FSharp.Data.GraphQL.IntegrationTests.fsproj @@ -7,6 +7,7 @@ + @@ -15,6 +16,7 @@ + @@ -22,7 +24,9 @@ + + @@ -30,16 +34,19 @@ + + + + + - ..\..\src\FSharp.Data.GraphQL.Client\bin\Debug\netstandard2.0\FSharp.Data.GraphQL.Client.dll - ..\..\src\FSharp.Data.GraphQL.Client\bin\Release\netstandard2.0\FSharp.Data.GraphQL.Client.dll + ..\..\src\FSharp.Data.GraphQL.Client\bin\$(Configuration)\netstandard2.0\FSharp.Data.GraphQL.Client.dll ..\..\bin\FSharp.Data.GraphQL.Client\netstandard2.0\FSharp.Data.GraphQL.Client.dll - ..\..\src\FSharp.Data.GraphQL.Client\bin\Debug\netstandard2.0\FSharp.Data.GraphQL.Shared.dll - ..\..\src\FSharp.Data.GraphQL.Client\bin\Release\netstandard2.0\FSharp.Data.GraphQL.Shared.dll - ..\..\bin\FSharp.Data.GraphQL.Client\netstandard2.0\FSharp.Data.GraphQL.Shared.dll + ..\..\src\FSharp.Data.GraphQL.Shared\bin\$(Configuration)\net10.0\FSharp.Data.GraphQL.Shared.dll + ..\..\bin\FSharp.Data.GraphQL.Shared\net10.0\FSharp.Data.GraphQL.Shared.dll diff --git a/tests/FSharp.Data.GraphQL.IntegrationTests/IntrospectionUpdateTests.fs b/tests/FSharp.Data.GraphQL.IntegrationTests/IntrospectionUpdateTests.fs new file mode 100644 index 000000000..c3931b295 --- /dev/null +++ b/tests/FSharp.Data.GraphQL.IntegrationTests/IntrospectionUpdateTests.fs @@ -0,0 +1,85 @@ +module FSharp.Data.GraphQL.IntegrationTests.IntrospectionUpdateTests + +open System +open System.IO +open System.Net.Http.Json +open System.Text.Json +open System.Threading +open Xunit + +let introspectionFilePath = + Path.Combine (__SOURCE_DIRECTORY__, "integration-introspection.json") + |> Path.GetFullPath + +let normalizeJsonDocument options (document : JsonDocument) = + use buffer = new MemoryStream () + use writer = new Utf8JsonWriter (buffer, options) + document.WriteTo writer + writer.Flush () + buffer.Seek (0L, SeekOrigin.Begin) |> ignore + JsonDocument.Parse buffer + +let parseAndNormalizeJsonAsync ct options stream = task { + let! document = JsonDocument.ParseAsync (stream, cancellationToken = ct) + return normalizeJsonDocument options document +} + +let areSchemasEqual (document1 : JsonDocument) (document2 : JsonDocument) = + let schema1 = document1.RootElement.GetProperty("data").GetProperty ("__schema") + let schema2 = document2.RootElement.GetProperty("data").GetProperty ("__schema") + schema1.GetRawText () = schema2.GetRawText () + +let readDestinationDocumentAsync ct (stream : FileStream) = task { + try + let! document = JsonDocument.ParseAsync (stream, cancellationToken = ct) + return ValueSome document + with :? JsonException -> + return ValueNone +} + +let updateIntrospectionFileAsync ct sourceStream = task { + use destinationStream = + new FileStream (introspectionFilePath, FileMode.OpenOrCreate, FileAccess.ReadWrite, FileShare.Read) + + let options = JsonWriterOptions (Indented = true) + let! sourceDocument = parseAndNormalizeJsonAsync ct options sourceStream + destinationStream.Seek (0L, SeekOrigin.Begin) |> ignore + let! destinationDocument = readDestinationDocumentAsync ct destinationStream + + let shouldUpdate = + match destinationDocument with + | ValueNone -> true + | ValueSome document -> not (areSchemasEqual document sourceDocument) + + if shouldUpdate then + destinationStream.Seek (0L, SeekOrigin.Begin) |> ignore + destinationStream.SetLength 0 + use writer = new Utf8JsonWriter (destinationStream, options) + sourceDocument.WriteTo writer + writer.Flush () + + return shouldUpdate +} + +[] +let ``Get GraphQL introspection response returns schema`` () = task { + use httpClient = TestHosts.createIntegrationHttpClient () + let! response = httpClient.GetFromJsonAsync ("/", CancellationToken.None) + let schema = response.GetProperty("data").GetProperty ("__schema") + Assert.NotEqual (Unchecked.defaultof, schema) + let hasErrors, _ = response.TryGetProperty "errors" + Assert.False hasErrors +} + +[] +let ``Update integration introspection file when schema changes`` () = task { + use httpClient = TestHosts.createIntegrationHttpClient () + let! sourceStream = httpClient.GetStreamAsync ("/") + let! wasUpdated = updateIntrospectionFileAsync CancellationToken.None sourceStream + Assert.True (File.Exists introspectionFilePath) + if wasUpdated then + let! sourceStreamSecondRun = httpClient.GetStreamAsync ("/") + use sourceStreamForVerification = sourceStreamSecondRun + let! wasUpdatedSecondRun = updateIntrospectionFileAsync CancellationToken.None sourceStreamForVerification + Assert.False wasUpdatedSecondRun +} diff --git a/tests/FSharp.Data.GraphQL.IntegrationTests/LocalProviderTests.fs b/tests/FSharp.Data.GraphQL.IntegrationTests/LocalProviderTests.fs index 897364cb1..d1a7381fd 100644 --- a/tests/FSharp.Data.GraphQL.IntegrationTests/LocalProviderTests.fs +++ b/tests/FSharp.Data.GraphQL.IntegrationTests/LocalProviderTests.fs @@ -5,13 +5,17 @@ open System.Threading.Tasks open FSharp.Data.GraphQL open Helpers -let [] ServerUrl = "http://localhost:8085" -let [] EmptyGuidAsString = "00000000-0000-0000-0000-000000000000" +[] +let IntrospectionPath = "integration-introspection.json" +[] +let EmptyGuidAsString = "00000000-0000-0000-0000-000000000000" -type Provider = GraphQLProvider +type Provider = GraphQLProvider // type FileProvider = GraphQLProvider -let context = Provider.GetContext(ServerUrl) +let connection = TestHosts.createIntegrationConnection () +let context = + Provider.GetContext (serverUrl = TestHosts.integrationServerUrl, connectionFactory = (fun () -> connection)) type Input = Provider.Types.Input type InputField = Provider.Types.InputField @@ -37,165 +41,175 @@ module SimpleOperation = uri deprecated guid - }""">() + }"""> () type Operation = Provider.Operations.Q let validateResult (input : Input option) (result : Operation.OperationResult) = result |> checkRequestTypeHeader "Classic" result.Data.IsSome |> equals true - input |> Option.iter (fun input -> + input + |> Option.iter (fun input -> result.Data.Value.Echo.IsSome |> equals true - input.List |> Option.iter (fun list -> + input.List + |> Option.iter (fun list -> result.Data.Value.Echo.Value.List.IsSome |> equals true - let input = list |> Array.map (fun x -> x.Int, x.IntOption, x.String, x.StringOption, x.Uri, x.Guid.ToString()) - let output = result.Data.Value.Echo.Value.List.Value |> Array.map (fun x -> x.Int, x.IntOption, x.String, x.StringOption, x.Uri, x.Guid) + let input = + list + |> Array.map (fun x -> x.Int, x.IntOption, x.String, x.StringOption, x.Uri, x.Guid.ToString ()) + let output = + result.Data.Value.Echo.Value.List.Value + |> Array.map (fun x -> x.Int, x.IntOption, x.String, x.StringOption, x.Uri, x.Guid) input |> equals output) - input.Single |> Option.iter (fun single -> + input.Single + |> Option.iter (fun single -> result.Data.Value.Echo.Value.Single.IsSome |> equals true - let input = single.Int, single.IntOption, single.String, single.StringOption, single.Uri, single.Guid.ToString() - let output = result.Data.Value.Echo.Value.Single.Value |> map (fun x -> x.Int, x.IntOption, x.String, x.StringOption, x.Uri, x.Guid) + let input = + single.Int, single.IntOption, single.String, single.StringOption, single.Uri, single.Guid.ToString () + let output = + result.Data.Value.Echo.Value.Single.Value + |> map (fun x -> x.Int, x.IntOption, x.String, x.StringOption, x.Uri, x.Guid) input |> equals output)) [] -let ``Should be able to execute a query without sending input field``() = - SimpleOperation.operation.Run() +let ``Should be able to execute a query without sending input field`` () = + SimpleOperation.operation.Run (context) |> SimpleOperation.validateResult None [] -let ``Should be able to execute a query using context, without sending input field``() = - SimpleOperation.operation.Run(context) +let ``Should be able to execute a query using context, without sending input field`` () = + SimpleOperation.operation.Run (context) |> SimpleOperation.validateResult None [] -let ``Should be able to execute a query without sending input field asynchronously``() : Task = task { - let! result = SimpleOperation.operation.AsyncRun() +let ``Should be able to execute a query without sending input field asynchronously`` () : Task = task { + let! result = SimpleOperation.operation.AsyncRun (context) result |> SimpleOperation.validateResult None } [] -let ``Should be able to execute a query using context, without sending input field, asynchronously``() : Task = task { - let! result = SimpleOperation.operation.AsyncRun(context) +let ``Should be able to execute a query using context, without sending input field, asynchronously`` () : Task = task { + let! result = SimpleOperation.operation.AsyncRun (context) result |> SimpleOperation.validateResult None } [] -let ``Should be able to execute a query sending an empty input field``() = - let input = Input() - SimpleOperation.operation.Run(input) +let ``Should be able to execute a query sending an empty input field`` () = + let input = Input () + SimpleOperation.operation.Run (context, input) |> SimpleOperation.validateResult (Some input) [] -let ``Should be able to execute a query using context, sending an empty input field``() = - let input = Input() - SimpleOperation.operation.Run(context, input) +let ``Should be able to execute a query using context, sending an empty input field`` () = + let input = Input () + SimpleOperation.operation.Run (context, input) |> SimpleOperation.validateResult (Some input) [] -let ``Should be able to execute a query without sending an empty input field asynchronously``() : Task = task { - let input = Input() - let! result = SimpleOperation.operation.AsyncRun(input) +let ``Should be able to execute a query without sending an empty input field asynchronously`` () : Task = task { + let input = Input () + let! result = SimpleOperation.operation.AsyncRun (context, input) result |> SimpleOperation.validateResult (Some input) } [] -let ``Should be able to execute a query using context, sending an empty input field, asynchronously``() : Task = task { - let input = Input() - let! result = SimpleOperation.operation.AsyncRun(context, input) +let ``Should be able to execute a query using context, sending an empty input field, asynchronously`` () : Task = task { + let input = Input () + let! result = SimpleOperation.operation.AsyncRun (context, input) result |> SimpleOperation.validateResult (Some input) } [] -let ``Should be able to execute a query sending an input field with single field``() = - let single = InputField("A", 2, System.Uri("http://localhost:1234"), EmptyGuidAsString) - let input = Input(single) - SimpleOperation.operation.Run(input) +let ``Should be able to execute a query sending an input field with single field`` () = + let single = InputField ("A", 2, System.Uri ("http://localhost:1234"), EmptyGuidAsString) + let input = Input (single) + SimpleOperation.operation.Run (context, input) |> SimpleOperation.validateResult (Some input) [] -let ``Should be able to execute a query using context, sending an input field with single field``() = - let single = InputField("A", 2, System.Uri("http://localhost:1234"), EmptyGuidAsString) - let input = Input(single) - SimpleOperation.operation.Run(context, input) +let ``Should be able to execute a query using context, sending an input field with single field`` () = + let single = InputField ("A", 2, System.Uri ("http://localhost:1234"), EmptyGuidAsString) + let input = Input (single) + SimpleOperation.operation.Run (context, input) |> SimpleOperation.validateResult (Some input) [] -let ``Should be able to execute a query without sending an input field with single field asynchronously``() : Task = task { - let single = InputField("A", 2, System.Uri("http://localhost:1234"), EmptyGuidAsString) - let input = Input(single) - let! result = SimpleOperation.operation.AsyncRun(input) +let ``Should be able to execute a query without sending an input field with single field asynchronously`` () : Task = task { + let single = InputField ("A", 2, System.Uri ("http://localhost:1234"), EmptyGuidAsString) + let input = Input (single) + let! result = SimpleOperation.operation.AsyncRun (context, input) result |> SimpleOperation.validateResult (Some input) } [] -let ``Should be able to execute a query using context, sending an input field with single field, asynchronously``() : Task = task { - let single = InputField("A", 2, System.Uri("http://localhost:1234"), EmptyGuidAsString) - let input = Input(single) - let! result = SimpleOperation.operation.AsyncRun(context, input) +let ``Should be able to execute a query using context, sending an input field with single field, asynchronously`` () : Task = task { + let single = InputField ("A", 2, System.Uri ("http://localhost:1234"), EmptyGuidAsString) + let input = Input (single) + let! result = SimpleOperation.operation.AsyncRun (context, input) result |> SimpleOperation.validateResult (Some input) } [] -let ``Should be able to execute a query sending an input field with list field``() = - let list = [|InputField("A", 2, System.Uri("http://localhost:4321"), EmptyGuidAsString)|] - let input = Input(list) - SimpleOperation.operation.Run(input) +let ``Should be able to execute a query sending an input field with list field`` () = + let list = [| InputField ("A", 2, System.Uri ("http://localhost:4321"), EmptyGuidAsString) |] + let input = Input (list) + SimpleOperation.operation.Run (context, input) |> SimpleOperation.validateResult (Some input) [] -let ``Should be able to execute a query using context, sending an input field with list field``() = - let list = [|InputField("A", 2, System.Uri("http://localhost:4321"), EmptyGuidAsString)|] - let input = Input(list) - SimpleOperation.operation.Run(context, input) +let ``Should be able to execute a query using context, sending an input field with list field`` () = + let list = [| InputField ("A", 2, System.Uri ("http://localhost:4321"), EmptyGuidAsString) |] + let input = Input (list) + SimpleOperation.operation.Run (context, input) |> SimpleOperation.validateResult (Some input) [] -let ``Should be able to execute a query without sending an input field with list field asynchronously``() : Task = task { - let list = [|InputField("A", 2, System.Uri("http://localhost:4321"), EmptyGuidAsString)|] - let input = Input(list) - let! result = SimpleOperation.operation.AsyncRun(input) +let ``Should be able to execute a query without sending an input field with list field asynchronously`` () : Task = task { + let list = [| InputField ("A", 2, System.Uri ("http://localhost:4321"), EmptyGuidAsString) |] + let input = Input (list) + let! result = SimpleOperation.operation.AsyncRun (context, input) result |> SimpleOperation.validateResult (Some input) } [] -let ``Should be able to execute a query using context, sending an input field with list field, asynchronously``() : Task = task { - let list = [|InputField("A", 2, System.Uri("http://localhost:4321"), EmptyGuidAsString)|] - let input = Input(list) - let! result = SimpleOperation.operation.AsyncRun(context, input) +let ``Should be able to execute a query using context, sending an input field with list field, asynchronously`` () : Task = task { + let list = [| InputField ("A", 2, System.Uri ("http://localhost:4321"), EmptyGuidAsString) |] + let input = Input (list) + let! result = SimpleOperation.operation.AsyncRun (context, input) result |> SimpleOperation.validateResult (Some input) } [] -let ``Should be able to execute a query sending an input field with single and list fields``() = - let single = InputField("A", 2, System.Uri("http://localhost:1234"), EmptyGuidAsString) - let list = [|InputField("A", 2, System.Uri("http://localhost:4321"), EmptyGuidAsString)|] - let input = Input(single, list) - SimpleOperation.operation.Run(input) +let ``Should be able to execute a query sending an input field with single and list fields`` () = + let single = InputField ("A", 2, System.Uri ("http://localhost:1234"), EmptyGuidAsString) + let list = [| InputField ("A", 2, System.Uri ("http://localhost:4321"), EmptyGuidAsString) |] + let input = Input (single, list) + SimpleOperation.operation.Run (context, input) |> SimpleOperation.validateResult (Some input) [] -let ``Should be able to execute a query using context, sending an input field with single and list fields``() = - let single = InputField("A", 2, System.Uri("http://localhost:1234"), EmptyGuidAsString) - let list = [|InputField("A", 2, System.Uri("http://localhost:4321"), EmptyGuidAsString)|] - let input = Input(single, list) - SimpleOperation.operation.Run(context, input) +let ``Should be able to execute a query using context, sending an input field with single and list fields`` () = + let single = InputField ("A", 2, System.Uri ("http://localhost:1234"), EmptyGuidAsString) + let list = [| InputField ("A", 2, System.Uri ("http://localhost:4321"), EmptyGuidAsString) |] + let input = Input (single, list) + SimpleOperation.operation.Run (context, input) |> SimpleOperation.validateResult (Some input) [] -let ``Should be able to execute a query without sending an input field with single and list fields asynchronously``() : Task = task { - let single = InputField("A", 2, System.Uri("http://localhost:1234"), EmptyGuidAsString) - let list = [|InputField("A", 2, System.Uri("http://localhost:4321"), EmptyGuidAsString)|] - let input = Input(single, list) - let! result = SimpleOperation.operation.AsyncRun(input) +let ``Should be able to execute a query without sending an input field with single and list fields asynchronously`` () : Task = task { + let single = InputField ("A", 2, System.Uri ("http://localhost:1234"), EmptyGuidAsString) + let list = [| InputField ("A", 2, System.Uri ("http://localhost:4321"), EmptyGuidAsString) |] + let input = Input (single, list) + let! result = SimpleOperation.operation.AsyncRun (context, input) result |> SimpleOperation.validateResult (Some input) } [] -let ``Should be able to execute a query using context, sending an input field with single and list fields, asynchronously``() : Task = task { - let single = InputField("A", 2, System.Uri("http://localhost:1234"), EmptyGuidAsString) - let list = [|InputField("A", 2, System.Uri("http://localhost:4321"), EmptyGuidAsString)|] - let input = Input(single, list) - let! result = SimpleOperation.operation.AsyncRun(context, input) +let ``Should be able to execute a query using context, sending an input field with single and list fields, asynchronously`` () : Task = task { + let single = InputField ("A", 2, System.Uri ("http://localhost:1234"), EmptyGuidAsString) + let list = [| InputField ("A", 2, System.Uri ("http://localhost:4321"), EmptyGuidAsString) |] + let input = Input (single, list) + let! result = SimpleOperation.operation.AsyncRun (context, input) result |> SimpleOperation.validateResult (Some input) } @@ -207,7 +221,7 @@ module SingleRequiredUploadOperation = contentType contentAsText } - }""">() + }"""> () type Operation = Provider.Operations.SingleUpload @@ -215,19 +229,29 @@ module SingleRequiredUploadOperation = result |> checkRequestTypeHeader "Multipart" result.Data.IsSome |> equals true result.Data.Value.SingleUpload.Name |> equals file.Name - result.Data.Value.SingleUpload.ContentAsText |> equals file.Content - result.Data.Value.SingleUpload.ContentType |> equals file.ContentType + result.Data.Value.SingleUpload.ContentAsText + |> equals file.Content + result.Data.Value.SingleUpload.ContentType + |> equals file.ContentType [] -let ``Should be able to execute a single required upload``() = - let file = { Name = "file.txt"; ContentType = "text/plain"; Content = "Sample text file contents" } - SingleRequiredUploadOperation.operation.Run(file.MakeUpload()) +let ``Should be able to execute a single required upload`` () = + let file = { + Name = "file.txt" + ContentType = "text/plain" + Content = "Sample text file contents" + } + SingleRequiredUploadOperation.operation.Run (context, file.MakeUpload ()) |> SingleRequiredUploadOperation.validateResult file [] -let ``Should be able to execute a single required upload asynchronously``() : Task = task { - let file = { Name = "file.txt"; ContentType = "text/plain"; Content = "Sample text file contents" } - let! result = SingleRequiredUploadOperation.operation.AsyncRun(file.MakeUpload()) +let ``Should be able to execute a single required upload asynchronously`` () : Task = task { + let file = { + Name = "file.txt" + ContentType = "text/plain" + Content = "Sample text file contents" + } + let! result = SingleRequiredUploadOperation.operation.AsyncRun (context, file.MakeUpload ()) result |> SingleRequiredUploadOperation.validateResult file } @@ -239,41 +263,54 @@ module SingleOptionalUploadOperation = contentType contentAsText } - }""">() + }"""> () type Operation = Provider.Operations.NullableSingleUpload let validateResult (file : File option) (result : Operation.OperationResult) = result |> checkRequestTypeHeader "Multipart" result.Data.IsSome |> equals true - file |> Option.iter (fun file -> - result.Data.Value.NullableSingleUpload.IsSome |> equals true - result.Data.Value.NullableSingleUpload.Value.Name |> equals file.Name - result.Data.Value.NullableSingleUpload.Value.ContentAsText |> equals file.Content - result.Data.Value.NullableSingleUpload.Value.ContentType |> equals file.ContentType) + file + |> Option.iter (fun file -> + result.Data.Value.NullableSingleUpload.IsSome |> equals true + result.Data.Value.NullableSingleUpload.Value.Name + |> equals file.Name + result.Data.Value.NullableSingleUpload.Value.ContentAsText + |> equals file.Content + result.Data.Value.NullableSingleUpload.Value.ContentType + |> equals file.ContentType) [] -let ``Should be able to execute a single optional upload by passing a file``() = - let file = { Name = "file.txt"; ContentType = "text/plain"; Content = "Sample text file contents" } - SingleOptionalUploadOperation.operation.Run(file.MakeUpload()) +let ``Should be able to execute a single optional upload by passing a file`` () = + let file = { + Name = "file.txt" + ContentType = "text/plain" + Content = "Sample text file contents" + } + SingleOptionalUploadOperation.operation.Run (context, file.MakeUpload ()) |> SingleOptionalUploadOperation.validateResult (Some file) -[] -let ``Should be able to execute a single optional upload by passing a file, asynchronously``() : Task = task { - let file = { Name = "file.txt"; ContentType = "text/plain"; Content = "Sample text file contents" } - let! result = SingleOptionalUploadOperation.operation.AsyncRun(file.MakeUpload()) - result |> SingleOptionalUploadOperation.validateResult (Some file) +[] +let ``Should be able to execute a single optional upload by passing a file, asynchronously`` () : Task = task { + let file = { + Name = "file.txt" + ContentType = "text/plain" + Content = "Sample text file contents" + } + let! result = SingleOptionalUploadOperation.operation.AsyncRun (context, file.MakeUpload ()) + result + |> SingleOptionalUploadOperation.validateResult (Some file) } -[] -let ``Should be able to execute a single optional upload by not passing a file``() = - SingleOptionalUploadOperation.operation.Run() +[] +let ``Should be able to execute a single optional upload by not passing a file`` () = + SingleOptionalUploadOperation.operation.Run (context) |> SingleOptionalUploadOperation.validateResult None [] -let ``Should be able to execute a single optional upload by not passing a file asynchronously``() : Task = task { - let! result = SingleOptionalUploadOperation.operation.AsyncRun() +let ``Should be able to execute a single optional upload by not passing a file asynchronously`` () : Task = task { + let! result = SingleOptionalUploadOperation.operation.AsyncRun (context) result |> SingleOptionalUploadOperation.validateResult None } @@ -285,33 +322,56 @@ module RequiredMultipleUploadOperation = contentType contentAsText } - }""">() + }"""> () type Operation = Provider.Operations.MultipleUpload - let validateResult (files : File []) (result : Operation.OperationResult) = + let validateResult (files : File[]) (result : Operation.OperationResult) = result |> checkRequestTypeHeader "Multipart" result.Data.IsSome |> equals true let receivedFiles = result.Data.Value.MultipleUpload - |> Array.map (fun file -> { Name = file.Name; ContentType = file.ContentType; Content = file.ContentAsText }) + |> Array.map (fun file -> { + Name = file.Name + ContentType = file.ContentType + Content = file.ContentAsText + }) receivedFiles |> equals files [] -let ``Should be able to execute a multiple required upload``() = - let files = - [| { Name = "file1.txt"; ContentType = "text/plain"; Content = "Sample text file contents 1" } - { Name = "file2.txt"; ContentType = "text/plain"; Content = "Sample text file contents 2" } |] - RequiredMultipleUploadOperation.operation.Run(files |> Array.map (fun f -> f.MakeUpload())) +let ``Should be able to execute a multiple required upload`` () = + let files = [| + { + Name = "file1.txt" + ContentType = "text/plain" + Content = "Sample text file contents 1" + } + { + Name = "file2.txt" + ContentType = "text/plain" + Content = "Sample text file contents 2" + } + |] + RequiredMultipleUploadOperation.operation.Run (context, files |> Array.map (fun f -> f.MakeUpload ())) |> RequiredMultipleUploadOperation.validateResult files [] -let ``Should be able to execute a multiple required upload asynchronously``() : Task = task { - let files = - [| { Name = "file1.txt"; ContentType = "text/plain"; Content = "Sample text file contents 1" } - { Name = "file2.txt"; ContentType = "text/plain"; Content = "Sample text file contents 2" } |] - let! result = RequiredMultipleUploadOperation.operation.AsyncRun(files |> Array.map (fun f -> f.MakeUpload())) - result |> RequiredMultipleUploadOperation.validateResult files +let ``Should be able to execute a multiple required upload asynchronously`` () : Task = task { + let files = [| + { + Name = "file1.txt" + ContentType = "text/plain" + Content = "Sample text file contents 1" + } + { + Name = "file2.txt" + ContentType = "text/plain" + Content = "Sample text file contents 2" + } + |] + let! result = RequiredMultipleUploadOperation.operation.AsyncRun (context, files |> Array.map (fun f -> f.MakeUpload ())) + result + |> RequiredMultipleUploadOperation.validateResult files } module OptionalMultipleUploadOperation = @@ -322,44 +382,70 @@ module OptionalMultipleUploadOperation = contentType contentAsText } - }""">() + }"""> () type Operation = Provider.Operations.NullableMultipleUpload - let validateResult (files : File [] option) (result : Operation.OperationResult) = + let validateResult (files : File[] option) (result : Operation.OperationResult) = result |> checkRequestTypeHeader "Multipart" result.Data.IsSome |> equals true let receivedFiles = result.Data.Value.NullableMultipleUpload - |> Option.map (Array.map (fun file -> { Name = file.Name; ContentType = file.ContentType; Content = file.ContentAsText })) + |> Option.map ( + Array.map (fun file -> { + Name = file.Name + ContentType = file.ContentType + Content = file.ContentAsText + }) + ) receivedFiles |> equals files [] -let ``Should be able to execute a multiple upload``() = - let files = - [| { Name = "file1.txt"; ContentType = "text/plain"; Content = "Sample text file contents 1" } - { Name = "file2.txt"; ContentType = "text/plain"; Content = "Sample text file contents 2" } |] - OptionalMultipleUploadOperation.operation.Run(files |> Array.map (fun f -> f.MakeUpload())) +let ``Should be able to execute a multiple upload`` () = + let files = [| + { + Name = "file1.txt" + ContentType = "text/plain" + Content = "Sample text file contents 1" + } + { + Name = "file2.txt" + ContentType = "text/plain" + Content = "Sample text file contents 2" + } + |] + OptionalMultipleUploadOperation.operation.Run (context, files |> Array.map (fun f -> f.MakeUpload ())) |> OptionalMultipleUploadOperation.validateResult (Some files) [] -let ``Should be able to execute a multiple upload asynchronously``() : Task = task { - let files = - [| { Name = "file1.txt"; ContentType = "text/plain"; Content = "Sample text file contents 1" } - { Name = "file2.txt"; ContentType = "text/plain"; Content = "Sample text file contents 2" } |] - let! result = OptionalMultipleUploadOperation.operation.AsyncRun(files |> Array.map (fun f -> f.MakeUpload())) - result |> OptionalMultipleUploadOperation.validateResult (Some files) +let ``Should be able to execute a multiple upload asynchronously`` () : Task = task { + let files = [| + { + Name = "file1.txt" + ContentType = "text/plain" + Content = "Sample text file contents 1" + } + { + Name = "file2.txt" + ContentType = "text/plain" + Content = "Sample text file contents 2" + } + |] + let! result = OptionalMultipleUploadOperation.operation.AsyncRun (context, files |> Array.map (fun f -> f.MakeUpload ())) + result + |> OptionalMultipleUploadOperation.validateResult (Some files) } [] -let ``Should be able to execute a multiple upload by sending no uploads``() = - OptionalMultipleUploadOperation.operation.Run() +let ``Should be able to execute a multiple upload by sending no uploads`` () = + OptionalMultipleUploadOperation.operation.Run (context) |> OptionalMultipleUploadOperation.validateResult None [] -let ``Should be able to execute a multiple upload asynchronously by sending no uploads``() : Task = task { - let! result = OptionalMultipleUploadOperation.operation.AsyncRun() - result |> OptionalMultipleUploadOperation.validateResult None +let ``Should be able to execute a multiple upload asynchronously by sending no uploads`` () : Task = task { + let! result = OptionalMultipleUploadOperation.operation.AsyncRun (context) + result + |> OptionalMultipleUploadOperation.validateResult None } module OptionalMultipleOptionalUploadOperation = @@ -370,65 +456,112 @@ module OptionalMultipleOptionalUploadOperation = contentType contentAsText } - }""">() + }"""> () type Operation = Provider.Operations.NullableMultipleNullableUpload - let validateResult (files : File option [] option) (result : Operation.OperationResult) = + let validateResult (files : File option[] option) (result : Operation.OperationResult) = result |> checkRequestTypeHeader "Multipart" result.Data.IsSome |> equals true let receivedFiles = result.Data.Value.NullableMultipleNullableUpload - |> Option.map (Array.map (Option.map (fun file -> { Name = file.Name; ContentType = file.ContentType; Content = file.ContentAsText }))) + |> Option.map ( + Array.map ( + Option.map (fun file -> { + Name = file.Name + ContentType = file.ContentType + Content = file.ContentAsText + }) + ) + ) receivedFiles |> equals files [] -let ``Should be able to execute a multiple optional upload``() = - let files = - [| Some { Name = "file1.txt"; ContentType = "text/plain"; Content = "Sample text file contents 1" } - Some { Name = "file2.txt"; ContentType = "text/plain"; Content = "Sample text file contents 2" } |] - OptionalMultipleOptionalUploadOperation.operation.Run(files |> Array.map (Option.map (fun f -> f.MakeUpload()))) +let ``Should be able to execute a multiple optional upload`` () = + let files = [| + Some { + Name = "file1.txt" + ContentType = "text/plain" + Content = "Sample text file contents 1" + } + Some { + Name = "file2.txt" + ContentType = "text/plain" + Content = "Sample text file contents 2" + } + |] + OptionalMultipleOptionalUploadOperation.operation.Run (context, files |> Array.map (Option.map (fun f -> f.MakeUpload ()))) |> OptionalMultipleOptionalUploadOperation.validateResult (Some files) [] -let ``Should be able to execute a multiple optional upload asynchronously``() : Task = task { - let files = - [| Some { Name = "file1.txt"; ContentType = "text/plain"; Content = "Sample text file contents 1" } - Some { Name = "file2.txt"; ContentType = "text/plain"; Content = "Sample text file contents 2" } |] - let! result = OptionalMultipleOptionalUploadOperation.operation.AsyncRun(files |> Array.map (Option.map (fun f -> f.MakeUpload()))) - result |> (OptionalMultipleOptionalUploadOperation.validateResult (Some files)) +let ``Should be able to execute a multiple optional upload asynchronously`` () : Task = task { + let files = [| + Some { + Name = "file1.txt" + ContentType = "text/plain" + Content = "Sample text file contents 1" + } + Some { + Name = "file2.txt" + ContentType = "text/plain" + Content = "Sample text file contents 2" + } + |] + let! result = OptionalMultipleOptionalUploadOperation.operation.AsyncRun (context, files |> Array.map (Option.map (fun f -> f.MakeUpload ()))) + result + |> (OptionalMultipleOptionalUploadOperation.validateResult (Some files)) } [] -let ``Should be able to execute a multiple optional upload by sending no uploads``() = - OptionalMultipleOptionalUploadOperation.operation.Run() +let ``Should be able to execute a multiple optional upload by sending no uploads`` () = + OptionalMultipleOptionalUploadOperation.operation.Run (context) |> OptionalMultipleOptionalUploadOperation.validateResult None [] -let ``Should be able to execute a multiple optional upload asynchronously by sending no uploads``() : Task = task { - let! result = OptionalMultipleOptionalUploadOperation.operation.AsyncRun() - result |> OptionalMultipleOptionalUploadOperation.validateResult None +let ``Should be able to execute a multiple optional upload asynchronously by sending no uploads`` () : Task = task { + let! result = OptionalMultipleOptionalUploadOperation.operation.AsyncRun (context) + result + |> OptionalMultipleOptionalUploadOperation.validateResult None } [] -let ``Should be able to execute a multiple optional upload by sending some uploads``() = - let files = - [| Some { Name = "file1.txt"; ContentType = "text/plain"; Content = "Sample text file contents 1" } - None - Some { Name = "file2.txt"; ContentType = "text/plain"; Content = "Sample text file contents 2" } - None |] - OptionalMultipleOptionalUploadOperation.operation.Run(files |> Array.map (Option.map (fun f -> f.MakeUpload()))) +let ``Should be able to execute a multiple optional upload by sending some uploads`` () = + let files = [| + Some { + Name = "file1.txt" + ContentType = "text/plain" + Content = "Sample text file contents 1" + } + None + Some { + Name = "file2.txt" + ContentType = "text/plain" + Content = "Sample text file contents 2" + } + None + |] + OptionalMultipleOptionalUploadOperation.operation.Run (context, files |> Array.map (Option.map (fun f -> f.MakeUpload ()))) |> OptionalMultipleOptionalUploadOperation.validateResult (Some files) [] -let ``Should be able to execute a multiple optional upload asynchronously by sending some uploads``() : Task = task { - let files = - [| Some { Name = "file1.txt"; ContentType = "text/plain"; Content = "Sample text file contents 1" } - None - Some { Name = "file2.txt"; ContentType = "text/plain"; Content = "Sample text file contents 2" } - None |] - let! result = OptionalMultipleOptionalUploadOperation.operation.AsyncRun(files |> Array.map (Option.map (fun f -> f.MakeUpload()))) - result |> OptionalMultipleOptionalUploadOperation.validateResult (Some files) +let ``Should be able to execute a multiple optional upload asynchronously by sending some uploads`` () : Task = task { + let files = [| + Some { + Name = "file1.txt" + ContentType = "text/plain" + Content = "Sample text file contents 1" + } + None + Some { + Name = "file2.txt" + ContentType = "text/plain" + Content = "Sample text file contents 2" + } + None + |] + let! result = OptionalMultipleOptionalUploadOperation.operation.AsyncRun (context, files |> Array.map (Option.map (fun f -> f.MakeUpload ()))) + result + |> OptionalMultipleOptionalUploadOperation.validateResult (Some files) } module UploadRequestOperation = @@ -454,7 +587,7 @@ module UploadRequestOperation = name contentType contentAsText - }""">() + }"""> () type Operation = Provider.Operations.UploadRequestOperation @@ -463,28 +596,66 @@ module UploadRequestOperation = let validateResult (request : FilesRequest) (result : Operation.OperationResult) = result |> checkRequestTypeHeader "Multipart" result.Data.IsSome |> equals true - result.Data.Value.UploadRequest.Single.ToDictionary() |> File.FromDictionary |> equals request.Single - result.Data.Value.UploadRequest.Multiple |> Array.map ((fun x -> x.ToDictionary()) >> File.FromDictionary) |> equals request.Multiple - result.Data.Value.UploadRequest.NullableMultiple |> Option.map (Array.map ((fun x -> x.ToDictionary()) >> File.FromDictionary)) |> equals request.NullableMultiple - result.Data.Value.UploadRequest.NullableMultipleNullable |> Option.map (Array.map (Option.map ((fun x -> x.ToDictionary()) >> File.FromDictionary))) |> equals request.NullableMultipleNullable + result.Data.Value.UploadRequest.Single.ToDictionary () + |> File.FromDictionary + |> equals request.Single + result.Data.Value.UploadRequest.Multiple + |> Array.map ((fun x -> x.ToDictionary ()) >> File.FromDictionary) + |> equals request.Multiple + result.Data.Value.UploadRequest.NullableMultiple + |> Option.map (Array.map ((fun x -> x.ToDictionary ()) >> File.FromDictionary)) + |> equals request.NullableMultiple + result.Data.Value.UploadRequest.NullableMultipleNullable + |> Option.map (Array.map (Option.map ((fun x -> x.ToDictionary ()) >> File.FromDictionary))) + |> equals request.NullableMultipleNullable [] -let ``Should be able to upload files inside another input type``() : Task = task { - let request = - { Single = { Name = "single.txt"; ContentType = "text/plain"; Content = "Single file content" } - Multiple = - [| { Name = "multiple1.txt"; ContentType = "text/plain"; Content = "Multiple files first file content" } - { Name = "multiple2.txt"; ContentType = "text/plain"; Content = "Multiple files second file content" } |] - NullableMultiple = Some [| { Name = "multiple3.txt"; ContentType = "text/plain"; Content = "Multiple files third file content" } |] - NullableMultipleNullable = - Some [| Some { Name = "multiple4.txt"; ContentType = "text/plain"; Content = "Multiple files fourth file content" }; None |] } +let ``Should be able to upload files inside another input type`` () : Task = task { + let request = { + Single = { + Name = "single.txt" + ContentType = "text/plain" + Content = "Single file content" + } + Multiple = [| + { + Name = "multiple1.txt" + ContentType = "text/plain" + Content = "Multiple files first file content" + } + { + Name = "multiple2.txt" + ContentType = "text/plain" + Content = "Multiple files second file content" + } + |] + NullableMultiple = + Some [| + { + Name = "multiple3.txt" + ContentType = "text/plain" + Content = "Multiple files third file content" + } + |] + NullableMultipleNullable = + Some [| + Some { + Name = "multiple4.txt" + ContentType = "text/plain" + Content = "Multiple files fourth file content" + } + None + |] + } let input = - let makeUpload (x : File) = x.MakeUpload() - UploadRequestOperation.Request(single = makeUpload request.Single, - multiple = Array.map makeUpload request.Multiple, - nullableMultiple = Array.map makeUpload request.NullableMultiple.Value, - nullableMultipleNullable = Array.map (Option.map makeUpload) request.NullableMultipleNullable.Value) - let! result = UploadRequestOperation.operation.AsyncRun(input) + let makeUpload (x : File) = x.MakeUpload () + UploadRequestOperation.Request ( + single = makeUpload request.Single, + multiple = Array.map makeUpload request.Multiple, + nullableMultiple = Array.map makeUpload request.NullableMultiple.Value, + nullableMultipleNullable = Array.map (Option.map makeUpload) request.NullableMultipleNullable.Value + ) + let! result = UploadRequestOperation.operation.AsyncRun (context, input) result |> UploadRequestOperation.validateResult request } @@ -492,7 +663,7 @@ module UploadComplexOperation = let operation = Provider.Operation<"""mutation UploadComplex($input: InputFile!) { uploadComplex(input: $input) - }""">() + }"""> () type Operation = Provider.Operations.UploadComplex type InputFile = Provider.Types.InputFile @@ -504,30 +675,46 @@ module UploadComplexOperation = [] let ``Should be able to upload file using complex input object`` () = - let file = { Name = "complex.txt"; ContentType = "text/plain"; Content = "Complex input object file content" } - let input = UploadComplexOperation.InputFile(file = file.MakeUpload()) - UploadComplexOperation.operation.Run(input) + let file = { + Name = "complex.txt" + ContentType = "text/plain" + Content = "Complex input object file content" + } + let input = UploadComplexOperation.InputFile (file = file.MakeUpload ()) + UploadComplexOperation.operation.Run (context, input) |> UploadComplexOperation.validateResult file [] let ``Should be able to upload file using complex input object with context`` () = - let file = { Name = "complex_context.txt"; ContentType = "text/plain"; Content = "Complex input with context file content" } - let input = UploadComplexOperation.InputFile(file = file.MakeUpload()) - UploadComplexOperation.operation.Run(context, input) + let file = { + Name = "complex_context.txt" + ContentType = "text/plain" + Content = "Complex input with context file content" + } + let input = UploadComplexOperation.InputFile (file = file.MakeUpload ()) + UploadComplexOperation.operation.Run (context, input) |> UploadComplexOperation.validateResult file [] let ``Should be able to upload file using complex input object asynchronously`` () : Task = task { - let file = { Name = "complex_async.txt"; ContentType = "text/plain"; Content = "Complex input object async file content" } - let input = UploadComplexOperation.InputFile(file = file.MakeUpload()) - let! result = UploadComplexOperation.operation.AsyncRun(input) + let file = { + Name = "complex_async.txt" + ContentType = "text/plain" + Content = "Complex input object async file content" + } + let input = UploadComplexOperation.InputFile (file = file.MakeUpload ()) + let! result = UploadComplexOperation.operation.AsyncRun (context, input) result |> UploadComplexOperation.validateResult file } [] let ``Should be able to upload file using complex input object with context asynchronously`` () : Task = task { - let file = { Name = "complex_context_async.txt"; ContentType = "text/plain"; Content = "Complex input with context async file content" } - let input = UploadComplexOperation.InputFile(file = file.MakeUpload()) - let! result = UploadComplexOperation.operation.AsyncRun(context, input) + let file = { + Name = "complex_context_async.txt" + ContentType = "text/plain" + Content = "Complex input with context async file content" + } + let input = UploadComplexOperation.InputFile (file = file.MakeUpload ()) + let! result = UploadComplexOperation.operation.AsyncRun (context, input) result |> UploadComplexOperation.validateResult file } diff --git a/tests/FSharp.Data.GraphQL.IntegrationTests/LocalProviderWithOptionalParametersOnlyTests.fs b/tests/FSharp.Data.GraphQL.IntegrationTests/LocalProviderWithOptionalParametersOnlyTests.fs index 74cc97a48..b0a0221ac 100644 --- a/tests/FSharp.Data.GraphQL.IntegrationTests/LocalProviderWithOptionalParametersOnlyTests.fs +++ b/tests/FSharp.Data.GraphQL.IntegrationTests/LocalProviderWithOptionalParametersOnlyTests.fs @@ -5,12 +5,16 @@ open System.Threading.Tasks open FSharp.Data.GraphQL open Helpers -let [] ServerUrl = "http://localhost:8085" -let [] EmptyGuidAsString = "00000000-0000-0000-0000-000000000000" +[] +let IntrospectionPath = "integration-introspection.json" +[] +let EmptyGuidAsString = "00000000-0000-0000-0000-000000000000" -type Provider = GraphQLProvider +type Provider = GraphQLProvider -let context = Provider.GetContext(ServerUrl) +let connection = TestHosts.createIntegrationConnection () +let context = + Provider.GetContext (serverUrl = TestHosts.integrationServerUrl, connectionFactory = (fun () -> connection)) type Input = Provider.Types.Input type InputField = Provider.Types.InputField @@ -36,165 +40,175 @@ module SimpleOperation = uri deprecated guid - }""">() + }"""> () type Operation = Provider.Operations.Q let validateResult (input : Input option) (result : Operation.OperationResult) = result |> checkRequestTypeHeader "Classic" result.Data.IsSome |> equals true - input |> Option.iter (fun input -> + input + |> Option.iter (fun input -> result.Data.Value.Echo.IsSome |> equals true - input.List |> Option.iter (fun list -> + input.List + |> Option.iter (fun list -> result.Data.Value.Echo.Value.List.IsSome |> equals true - let input = list |> Array.map (fun x -> x.Int, x.IntOption, x.String, x.StringOption, x.Uri, x.Guid.ToString()) - let output = result.Data.Value.Echo.Value.List.Value |> Array.map (fun x -> x.Int, x.IntOption, x.String, x.StringOption, x.Uri, x.Guid) + let input = + list + |> Array.map (fun x -> x.Int, x.IntOption, x.String, x.StringOption, x.Uri, x.Guid.ToString ()) + let output = + result.Data.Value.Echo.Value.List.Value + |> Array.map (fun x -> x.Int, x.IntOption, x.String, x.StringOption, x.Uri, x.Guid) input |> equals output) - input.Single |> Option.iter (fun single -> + input.Single + |> Option.iter (fun single -> result.Data.Value.Echo.Value.Single.IsSome |> equals true - let input = single.Int, single.IntOption, single.String, single.StringOption, single.Uri, single.Guid.ToString() - let output = result.Data.Value.Echo.Value.Single.Value |> map (fun x -> x.Int, x.IntOption, x.String, x.StringOption, x.Uri, x.Guid) + let input = + single.Int, single.IntOption, single.String, single.StringOption, single.Uri, single.Guid.ToString () + let output = + result.Data.Value.Echo.Value.Single.Value + |> map (fun x -> x.Int, x.IntOption, x.String, x.StringOption, x.Uri, x.Guid) input |> equals output)) [] -let ``Should be able to execute a query without sending input field``() = - SimpleOperation.operation.Run() +let ``Should be able to execute a query without sending input field`` () = + SimpleOperation.operation.Run (context) |> SimpleOperation.validateResult None [] -let ``Should be able to execute a query using context, without sending input field``() = - SimpleOperation.operation.Run(context) +let ``Should be able to execute a query using context, without sending input field`` () = + SimpleOperation.operation.Run (context) |> SimpleOperation.validateResult None [] -let ``Should be able to execute a query without sending input field asynchronously``() = - SimpleOperation.operation.AsyncRun() +let ``Should be able to execute a query without sending input field asynchronously`` () = + SimpleOperation.operation.AsyncRun (context) |> Async.RunSynchronously |> SimpleOperation.validateResult None [] -let ``Should be able to execute a query using context, without sending input field, asynchronously``() : Task = task { - let! result = SimpleOperation.operation.AsyncRun(context) +let ``Should be able to execute a query using context, without sending input field, asynchronously`` () : Task = task { + let! result = SimpleOperation.operation.AsyncRun (context) result |> SimpleOperation.validateResult None } [] -let ``Should be able to execute a query sending an empty input field``() = - let input = Input() - SimpleOperation.operation.Run(Some input) +let ``Should be able to execute a query sending an empty input field`` () = + let input = Input () + SimpleOperation.operation.Run (context, Some input) |> SimpleOperation.validateResult (Some input) [] -let ``Should be able to execute a query using context, sending an empty input field``() = - let input = Input() - SimpleOperation.operation.Run(context, Some input) +let ``Should be able to execute a query using context, sending an empty input field`` () = + let input = Input () + SimpleOperation.operation.Run (context, Some input) |> SimpleOperation.validateResult (Some input) [] -let ``Should be able to execute a query without sending an empty input field asynchronously``() : Task = task { - let input = Input() - let! result = SimpleOperation.operation.AsyncRun(Some input) +let ``Should be able to execute a query without sending an empty input field asynchronously`` () : Task = task { + let input = Input () + let! result = SimpleOperation.operation.AsyncRun (context, Some input) result |> SimpleOperation.validateResult (Some input) } [] -let ``Should be able to execute a query using context, sending an empty input field, asynchronously``() : Task = task { - let input = Input() - let! result = SimpleOperation.operation.AsyncRun(context, Some input) +let ``Should be able to execute a query using context, sending an empty input field, asynchronously`` () : Task = task { + let input = Input () + let! result = SimpleOperation.operation.AsyncRun (context, Some input) result |> SimpleOperation.validateResult (Some input) } [] -let ``Should be able to execute a query sending an input field with single field``() = - let single = InputField("A", 2, System.Uri("http://localhost:1234"), EmptyGuidAsString) - let input = Input(Some single) - SimpleOperation.operation.Run(Some input) +let ``Should be able to execute a query sending an input field with single field`` () = + let single = InputField ("A", 2, System.Uri ("http://localhost:1234"), EmptyGuidAsString) + let input = Input (Some single) + SimpleOperation.operation.Run (context, Some input) |> SimpleOperation.validateResult (Some input) [] -let ``Should be able to execute a query using context, sending an input field with single field``() = - let single = InputField("A", 2, System.Uri("http://localhost:1234"), EmptyGuidAsString) - let input = Input(Some single) - SimpleOperation.operation.Run(context, Some input) +let ``Should be able to execute a query using context, sending an input field with single field`` () = + let single = InputField ("A", 2, System.Uri ("http://localhost:1234"), EmptyGuidAsString) + let input = Input (Some single) + SimpleOperation.operation.Run (context, Some input) |> SimpleOperation.validateResult (Some input) [] -let ``Should be able to execute a query without sending an input field with single field asynchronously``() : Task = task { - let single = InputField("A", 2, System.Uri("http://localhost:1234"), EmptyGuidAsString) - let input = Input(Some single) - let! result = SimpleOperation.operation.AsyncRun(Some input) +let ``Should be able to execute a query without sending an input field with single field asynchronously`` () : Task = task { + let single = InputField ("A", 2, System.Uri ("http://localhost:1234"), EmptyGuidAsString) + let input = Input (Some single) + let! result = SimpleOperation.operation.AsyncRun (context, Some input) result |> SimpleOperation.validateResult (Some input) } [] -let ``Should be able to execute a query using context, sending an input field with single field, asynchronously``() : Task = task { - let single = InputField("A", 2, System.Uri("http://localhost:1234"), EmptyGuidAsString) - let input = Input(Some single) - let! result = SimpleOperation.operation.AsyncRun(context, Some input) +let ``Should be able to execute a query using context, sending an input field with single field, asynchronously`` () : Task = task { + let single = InputField ("A", 2, System.Uri ("http://localhost:1234"), EmptyGuidAsString) + let input = Input (Some single) + let! result = SimpleOperation.operation.AsyncRun (context, Some input) result |> SimpleOperation.validateResult (Some input) } [] -let ``Should be able to execute a query sending an input field with list field``() = - let list = [|InputField("A", 2, System.Uri("http://localhost:4321"), EmptyGuidAsString)|] - let input = Input(list = Some list) - SimpleOperation.operation.Run(Some input) +let ``Should be able to execute a query sending an input field with list field`` () = + let list = [| InputField ("A", 2, System.Uri ("http://localhost:4321"), EmptyGuidAsString) |] + let input = Input (list = Some list) + SimpleOperation.operation.Run (context, Some input) |> SimpleOperation.validateResult (Some input) [] -let ``Should be able to execute a query using context, sending an input field with list field``() = - let list = [|InputField("A", 2, System.Uri("http://localhost:4321"), EmptyGuidAsString)|] - let input = Input(list = Some list) - SimpleOperation.operation.Run(context, Some input) +let ``Should be able to execute a query using context, sending an input field with list field`` () = + let list = [| InputField ("A", 2, System.Uri ("http://localhost:4321"), EmptyGuidAsString) |] + let input = Input (list = Some list) + SimpleOperation.operation.Run (context, Some input) |> SimpleOperation.validateResult (Some input) [] -let ``Should be able to execute a query without sending an input field with list field asynchronously``() : Task = task { - let list = [|InputField("A", 2, System.Uri("http://localhost:4321"), EmptyGuidAsString)|] - let input = Input(list = Some list) - let! result = SimpleOperation.operation.AsyncRun(Some input) +let ``Should be able to execute a query without sending an input field with list field asynchronously`` () : Task = task { + let list = [| InputField ("A", 2, System.Uri ("http://localhost:4321"), EmptyGuidAsString) |] + let input = Input (list = Some list) + let! result = SimpleOperation.operation.AsyncRun (context, Some input) result |> SimpleOperation.validateResult (Some input) } [] -let ``Should be able to execute a query using context, sending an input field with list field, asynchronously``() : Task = task { - let list = [|InputField("A", 2, System.Uri("http://localhost:4321"), EmptyGuidAsString)|] - let input = Input(list = Some list) - let! result = SimpleOperation.operation.AsyncRun(context, Some input) +let ``Should be able to execute a query using context, sending an input field with list field, asynchronously`` () : Task = task { + let list = [| InputField ("A", 2, System.Uri ("http://localhost:4321"), EmptyGuidAsString) |] + let input = Input (list = Some list) + let! result = SimpleOperation.operation.AsyncRun (context, Some input) result |> SimpleOperation.validateResult (Some input) } [] -let ``Should be able to execute a query sending an input field with single and list fields``() = - let single = InputField("A", 2, System.Uri("http://localhost:1234"), EmptyGuidAsString) - let list = [|InputField("A", 2, System.Uri("http://localhost:4321"), EmptyGuidAsString)|] - let input = Input(Some single, Some list) - SimpleOperation.operation.Run(Some input) +let ``Should be able to execute a query sending an input field with single and list fields`` () = + let single = InputField ("A", 2, System.Uri ("http://localhost:1234"), EmptyGuidAsString) + let list = [| InputField ("A", 2, System.Uri ("http://localhost:4321"), EmptyGuidAsString) |] + let input = Input (Some single, Some list) + SimpleOperation.operation.Run (context, Some input) |> SimpleOperation.validateResult (Some input) [] -let ``Should be able to execute a query using context, sending an input field with single and list fields``() = - let single = InputField("A", 2, System.Uri("http://localhost:1234"), EmptyGuidAsString) - let list = [|InputField("A", 2, System.Uri("http://localhost:4321"), EmptyGuidAsString)|] - let input = Input(Some single, Some list) - SimpleOperation.operation.Run(context, Some input) +let ``Should be able to execute a query using context, sending an input field with single and list fields`` () = + let single = InputField ("A", 2, System.Uri ("http://localhost:1234"), EmptyGuidAsString) + let list = [| InputField ("A", 2, System.Uri ("http://localhost:4321"), EmptyGuidAsString) |] + let input = Input (Some single, Some list) + SimpleOperation.operation.Run (context, Some input) |> SimpleOperation.validateResult (Some input) [] -let ``Should be able to execute a query without sending an input field with single and list fields asynchronously``() : Task = task { - let single = InputField("A", 2, System.Uri("http://localhost:1234"), EmptyGuidAsString) - let list = [|InputField("A", 2, System.Uri("http://localhost:4321"), EmptyGuidAsString)|] - let input = Input(Some single, Some list) - let! result = SimpleOperation.operation.AsyncRun(Some input) +let ``Should be able to execute a query without sending an input field with single and list fields asynchronously`` () : Task = task { + let single = InputField ("A", 2, System.Uri ("http://localhost:1234"), EmptyGuidAsString) + let list = [| InputField ("A", 2, System.Uri ("http://localhost:4321"), EmptyGuidAsString) |] + let input = Input (Some single, Some list) + let! result = SimpleOperation.operation.AsyncRun (context, Some input) result |> SimpleOperation.validateResult (Some input) } [] -let ``Should be able to execute a query using context, sending an input field with single and list fields, asynchronously``() : Task = task { - let single = InputField("A", 2, System.Uri("http://localhost:1234"), EmptyGuidAsString) - let list = [|InputField("A", 2, System.Uri("http://localhost:4321"), EmptyGuidAsString)|] - let input = Input(Some single, Some list) - let! result = SimpleOperation.operation.AsyncRun(context, Some input) +let ``Should be able to execute a query using context, sending an input field with single and list fields, asynchronously`` () : Task = task { + let single = InputField ("A", 2, System.Uri ("http://localhost:1234"), EmptyGuidAsString) + let list = [| InputField ("A", 2, System.Uri ("http://localhost:4321"), EmptyGuidAsString) |] + let input = Input (Some single, Some list) + let! result = SimpleOperation.operation.AsyncRun (context, Some input) result |> SimpleOperation.validateResult (Some input) } @@ -206,7 +220,7 @@ module SingleRequiredUploadOperation = contentType contentAsText } - }""">() + }"""> () type Operation = Provider.Operations.SingleUpload @@ -214,19 +228,29 @@ module SingleRequiredUploadOperation = result |> checkRequestTypeHeader "Multipart" result.Data.IsSome |> equals true result.Data.Value.SingleUpload.Name |> equals file.Name - result.Data.Value.SingleUpload.ContentAsText |> equals file.Content - result.Data.Value.SingleUpload.ContentType |> equals file.ContentType + result.Data.Value.SingleUpload.ContentAsText + |> equals file.Content + result.Data.Value.SingleUpload.ContentType + |> equals file.ContentType [] -let ``Should be able to execute a single required upload``() = - let file = { Name = "file.txt"; ContentType = "text/plain"; Content = "Sample text file contents" } - SingleRequiredUploadOperation.operation.Run(file.MakeUpload(file.Name)) +let ``Should be able to execute a single required upload`` () = + let file = { + Name = "file.txt" + ContentType = "text/plain" + Content = "Sample text file contents" + } + SingleRequiredUploadOperation.operation.Run (context, file.MakeUpload (file.Name)) |> SingleRequiredUploadOperation.validateResult file [] -let ``Should be able to execute a single required upload asynchronously``() : Task = task { - let file = { Name = "file.txt"; ContentType = "text/plain"; Content = "Sample text file contents" } - let! result = SingleRequiredUploadOperation.operation.AsyncRun(file.MakeUpload()) +let ``Should be able to execute a single required upload asynchronously`` () : Task = task { + let file = { + Name = "file.txt" + ContentType = "text/plain" + Content = "Sample text file contents" + } + let! result = SingleRequiredUploadOperation.operation.AsyncRun (context, file.MakeUpload ()) result |> SingleRequiredUploadOperation.validateResult file } @@ -238,40 +262,53 @@ module SingleOptionalUploadOperation = contentType contentAsText } - }""">() + }"""> () type Operation = Provider.Operations.NullableSingleUpload let validateResult (file : File option) (result : Operation.OperationResult) = result |> checkRequestTypeHeader "Multipart" result.Data.IsSome |> equals true - file |> Option.iter (fun file -> - result.Data.Value.NullableSingleUpload.IsSome |> equals true - result.Data.Value.NullableSingleUpload.Value.Name |> equals file.Name - result.Data.Value.NullableSingleUpload.Value.ContentAsText |> equals file.Content - result.Data.Value.NullableSingleUpload.Value.ContentType |> equals file.ContentType) + file + |> Option.iter (fun file -> + result.Data.Value.NullableSingleUpload.IsSome |> equals true + result.Data.Value.NullableSingleUpload.Value.Name + |> equals file.Name + result.Data.Value.NullableSingleUpload.Value.ContentAsText + |> equals file.Content + result.Data.Value.NullableSingleUpload.Value.ContentType + |> equals file.ContentType) [] -let ``Should be able to execute a single optional upload by passing a file``() = - let file = { Name = "file.txt"; ContentType = "text/plain"; Content = "Sample text file contents" } - SingleOptionalUploadOperation.operation.Run(file.MakeUpload() |> Some) +let ``Should be able to execute a single optional upload by passing a file`` () = + let file = { + Name = "file.txt" + ContentType = "text/plain" + Content = "Sample text file contents" + } + SingleOptionalUploadOperation.operation.Run (context, file.MakeUpload () |> Some) |> SingleOptionalUploadOperation.validateResult (Some file) [] -let ``Should be able to execute a single optional upload by passing a file, asynchronously``() : Task = task { - let file = { Name = "file.txt"; ContentType = "text/plain"; Content = "Sample text file contents" } - let! result = SingleOptionalUploadOperation.operation.AsyncRun(file.MakeUpload("test") |> Some) - result |> SingleOptionalUploadOperation.validateResult (Some file) +let ``Should be able to execute a single optional upload by passing a file, asynchronously`` () : Task = task { + let file = { + Name = "file.txt" + ContentType = "text/plain" + Content = "Sample text file contents" + } + let! result = SingleOptionalUploadOperation.operation.AsyncRun (context, file.MakeUpload ("test") |> Some) + result + |> SingleOptionalUploadOperation.validateResult (Some file) } [] -let ``Should be able to execute a single optional upload by not passing a file``() = - SingleOptionalUploadOperation.operation.Run() +let ``Should be able to execute a single optional upload by not passing a file`` () = + SingleOptionalUploadOperation.operation.Run (context) |> SingleOptionalUploadOperation.validateResult None [] -let ``Should be able to execute a single optional upload by not passing a file asynchronously``() : Task = task { - let! result = SingleOptionalUploadOperation.operation.AsyncRun() +let ``Should be able to execute a single optional upload by not passing a file asynchronously`` () : Task = task { + let! result = SingleOptionalUploadOperation.operation.AsyncRun (context) result |> SingleOptionalUploadOperation.validateResult None } @@ -283,33 +320,56 @@ module RequiredMultipleUploadOperation = contentType contentAsText } - }""">() + }"""> () type Operation = Provider.Operations.MultipleUpload - let validateResult (files : File []) (result : Operation.OperationResult) = + let validateResult (files : File[]) (result : Operation.OperationResult) = result |> checkRequestTypeHeader "Multipart" result.Data.IsSome |> equals true let receivedFiles = result.Data.Value.MultipleUpload - |> Array.map (fun file -> { Name = file.Name; ContentType = file.ContentType; Content = file.ContentAsText }) + |> Array.map (fun file -> { + Name = file.Name + ContentType = file.ContentType + Content = file.ContentAsText + }) receivedFiles |> equals files [] -let ``Should be able to execute a multiple required upload``() = - let files = - [| { Name = "file1.txt"; ContentType = "text/plain"; Content = "Sample text file contents 1" } - { Name = "file2.txt"; ContentType = "text/plain"; Content = "Sample text file contents 2" } |] - RequiredMultipleUploadOperation.operation.Run(files |> Array.map (fun f -> f.MakeUpload())) +let ``Should be able to execute a multiple required upload`` () = + let files = [| + { + Name = "file1.txt" + ContentType = "text/plain" + Content = "Sample text file contents 1" + } + { + Name = "file2.txt" + ContentType = "text/plain" + Content = "Sample text file contents 2" + } + |] + RequiredMultipleUploadOperation.operation.Run (context, files |> Array.map (fun f -> f.MakeUpload ())) |> RequiredMultipleUploadOperation.validateResult files [] -let ``Should be able to execute a multiple required upload asynchronously``() : Task = task { - let files = - [| { Name = "file1.txt"; ContentType = "text/plain"; Content = "Sample text file contents 1" } - { Name = "file2.txt"; ContentType = "text/plain"; Content = "Sample text file contents 2" } |] - let! result = RequiredMultipleUploadOperation.operation.AsyncRun(files |> Array.map (fun f -> f.MakeUpload())) - result |> RequiredMultipleUploadOperation.validateResult files +let ``Should be able to execute a multiple required upload asynchronously`` () : Task = task { + let files = [| + { + Name = "file1.txt" + ContentType = "text/plain" + Content = "Sample text file contents 1" + } + { + Name = "file2.txt" + ContentType = "text/plain" + Content = "Sample text file contents 2" + } + |] + let! result = RequiredMultipleUploadOperation.operation.AsyncRun (context, files |> Array.map (fun f -> f.MakeUpload ())) + result + |> RequiredMultipleUploadOperation.validateResult files } module OptionalMultipleUploadOperation = @@ -320,44 +380,70 @@ module OptionalMultipleUploadOperation = contentType contentAsText } - }""">() + }"""> () type Operation = Provider.Operations.NullableMultipleUpload - let validateResult (files : File [] option) (result : Operation.OperationResult) = + let validateResult (files : File[] option) (result : Operation.OperationResult) = result |> checkRequestTypeHeader "Multipart" result.Data.IsSome |> equals true let receivedFiles = result.Data.Value.NullableMultipleUpload - |> Option.map (Array.map (fun file -> { Name = file.Name; ContentType = file.ContentType; Content = file.ContentAsText })) + |> Option.map ( + Array.map (fun file -> { + Name = file.Name + ContentType = file.ContentType + Content = file.ContentAsText + }) + ) receivedFiles |> equals files [] -let ``Should be able to execute a multiple upload``() = - let files = - [| { Name = "file1.txt"; ContentType = "text/plain"; Content = "Sample text file contents 1" } - { Name = "file2.txt"; ContentType = "text/plain"; Content = "Sample text file contents 2" } |] - OptionalMultipleUploadOperation.operation.Run(files |> Array.map (fun f -> f.MakeUpload()) |> Some) +let ``Should be able to execute a multiple upload`` () = + let files = [| + { + Name = "file1.txt" + ContentType = "text/plain" + Content = "Sample text file contents 1" + } + { + Name = "file2.txt" + ContentType = "text/plain" + Content = "Sample text file contents 2" + } + |] + OptionalMultipleUploadOperation.operation.Run (context, files |> Array.map (fun f -> f.MakeUpload ()) |> Some) |> OptionalMultipleUploadOperation.validateResult (Some files) [] -let ``Should be able to execute a multiple upload asynchronously``() : Task = task { - let files = - [| { Name = "file1.txt"; ContentType = "text/plain"; Content = "Sample text file contents 1" } - { Name = "file2.txt"; ContentType = "text/plain"; Content = "Sample text file contents 2" } |] - let! result = OptionalMultipleUploadOperation.operation.AsyncRun((files |> Array.map _.MakeUpload()) |> Some) - result |> OptionalMultipleUploadOperation.validateResult (Some files) +let ``Should be able to execute a multiple upload asynchronously`` () : Task = task { + let files = [| + { + Name = "file1.txt" + ContentType = "text/plain" + Content = "Sample text file contents 1" + } + { + Name = "file2.txt" + ContentType = "text/plain" + Content = "Sample text file contents 2" + } + |] + let! result = OptionalMultipleUploadOperation.operation.AsyncRun (context, (files |> Array.map _.MakeUpload()) |> Some) + result + |> OptionalMultipleUploadOperation.validateResult (Some files) } [] -let ``Should be able to execute a multiple upload by sending no uploads``() = - OptionalMultipleUploadOperation.operation.Run() +let ``Should be able to execute a multiple upload by sending no uploads`` () = + OptionalMultipleUploadOperation.operation.Run (context) |> OptionalMultipleUploadOperation.validateResult None [] -let ``Should be able to execute a multiple upload asynchronously by sending no uploads``() : Task = task { - let! result = OptionalMultipleUploadOperation.operation.AsyncRun() - result |> OptionalMultipleUploadOperation.validateResult None +let ``Should be able to execute a multiple upload asynchronously by sending no uploads`` () : Task = task { + let! result = OptionalMultipleUploadOperation.operation.AsyncRun (context) + result + |> OptionalMultipleUploadOperation.validateResult None } module OptionalMultipleOptionalUploadOperation = @@ -368,65 +454,112 @@ module OptionalMultipleOptionalUploadOperation = contentType contentAsText } - }""">() + }"""> () type Operation = Provider.Operations.NullableMultipleNullableUpload - let validateResult (files : File option [] option) (result : Operation.OperationResult) = + let validateResult (files : File option[] option) (result : Operation.OperationResult) = result |> checkRequestTypeHeader "Multipart" result.Data.IsSome |> equals true let receivedFiles = result.Data.Value.NullableMultipleNullableUpload - |> Option.map (Array.map (Option.map (fun file -> { Name = file.Name; ContentType = file.ContentType; Content = file.ContentAsText }))) + |> Option.map ( + Array.map ( + Option.map (fun file -> { + Name = file.Name + ContentType = file.ContentType + Content = file.ContentAsText + }) + ) + ) receivedFiles |> equals files [] -let ``Should be able to execute a multiple optional upload``() = - let files = - [| Some { Name = "file1.txt"; ContentType = "text/plain"; Content = "Sample text file contents 1" } - Some { Name = "file2.txt"; ContentType = "text/plain"; Content = "Sample text file contents 2" } |] - OptionalMultipleOptionalUploadOperation.operation.Run((files |> Array.map (Option.map _.MakeUpload())) |> Some) +let ``Should be able to execute a multiple optional upload`` () = + let files = [| + Some { + Name = "file1.txt" + ContentType = "text/plain" + Content = "Sample text file contents 1" + } + Some { + Name = "file2.txt" + ContentType = "text/plain" + Content = "Sample text file contents 2" + } + |] + OptionalMultipleOptionalUploadOperation.operation.Run (context, (files |> Array.map (Option.map _.MakeUpload())) |> Some) |> OptionalMultipleOptionalUploadOperation.validateResult (Some files) [] -let ``Should be able to execute a multiple optional upload asynchronously``() : Task = task { - let files = - [| Some { Name = "file1.txt"; ContentType = "text/plain"; Content = "Sample text file contents 1" } - Some { Name = "file2.txt"; ContentType = "text/plain"; Content = "Sample text file contents 2" } |] - let! result = OptionalMultipleOptionalUploadOperation.operation.AsyncRun((files |> Array.map (Option.map _.MakeUpload())) |> Some) - result |> OptionalMultipleOptionalUploadOperation.validateResult (Some files) +let ``Should be able to execute a multiple optional upload asynchronously`` () : Task = task { + let files = [| + Some { + Name = "file1.txt" + ContentType = "text/plain" + Content = "Sample text file contents 1" + } + Some { + Name = "file2.txt" + ContentType = "text/plain" + Content = "Sample text file contents 2" + } + |] + let! result = OptionalMultipleOptionalUploadOperation.operation.AsyncRun (context, (files |> Array.map (Option.map _.MakeUpload())) |> Some) + result + |> OptionalMultipleOptionalUploadOperation.validateResult (Some files) } [] -let ``Should be able to execute a multiple optional upload by sending no uploads``() = - OptionalMultipleOptionalUploadOperation.operation.Run() +let ``Should be able to execute a multiple optional upload by sending no uploads`` () = + OptionalMultipleOptionalUploadOperation.operation.Run (context) |> OptionalMultipleOptionalUploadOperation.validateResult None [] -let ``Should be able to execute a multiple optional upload asynchronously by sending no uploads``() : Task = task { - let! result = OptionalMultipleOptionalUploadOperation.operation.AsyncRun() - result |> OptionalMultipleOptionalUploadOperation.validateResult None +let ``Should be able to execute a multiple optional upload asynchronously by sending no uploads`` () : Task = task { + let! result = OptionalMultipleOptionalUploadOperation.operation.AsyncRun (context) + result + |> OptionalMultipleOptionalUploadOperation.validateResult None } [] -let ``Should be able to execute a multiple optional upload by sending some uploads``() = - let files = - [| Some { Name = "file1.txt"; ContentType = "text/plain"; Content = "Sample text file contents 1" } - None - Some { Name = "file2.txt"; ContentType = "text/plain"; Content = "Sample text file contents 2" } - None |] - OptionalMultipleOptionalUploadOperation.operation.Run(files |> Array.map (Option.map _.MakeUpload()) |> Some) +let ``Should be able to execute a multiple optional upload by sending some uploads`` () = + let files = [| + Some { + Name = "file1.txt" + ContentType = "text/plain" + Content = "Sample text file contents 1" + } + None + Some { + Name = "file2.txt" + ContentType = "text/plain" + Content = "Sample text file contents 2" + } + None + |] + OptionalMultipleOptionalUploadOperation.operation.Run (context, files |> Array.map (Option.map _.MakeUpload()) |> Some) |> OptionalMultipleOptionalUploadOperation.validateResult (Some files) [] -let ``Should be able to execute a multiple optional upload asynchronously by sending some uploads``() : Task = task { - let files = - [| Some { Name = "file1.txt"; ContentType = "text/plain"; Content = "Sample text file contents 1" } - None - Some { Name = "file2.txt"; ContentType = "text/plain"; Content = "Sample text file contents 2" } - None |] - let! result = OptionalMultipleOptionalUploadOperation.operation.AsyncRun(files |> Array.map (Option.map _.MakeUpload()) |> Some) - result |> OptionalMultipleOptionalUploadOperation.validateResult (Some files) +let ``Should be able to execute a multiple optional upload asynchronously by sending some uploads`` () : Task = task { + let files = [| + Some { + Name = "file1.txt" + ContentType = "text/plain" + Content = "Sample text file contents 1" + } + None + Some { + Name = "file2.txt" + ContentType = "text/plain" + Content = "Sample text file contents 2" + } + None + |] + let! result = OptionalMultipleOptionalUploadOperation.operation.AsyncRun (context, files |> Array.map (Option.map _.MakeUpload()) |> Some) + result + |> OptionalMultipleOptionalUploadOperation.validateResult (Some files) } module UploadRequestOperation = @@ -452,7 +585,7 @@ module UploadRequestOperation = name contentType contentAsText - }""">() + }"""> () type Operation = Provider.Operations.UploadRequestOperation @@ -461,35 +594,73 @@ module UploadRequestOperation = let validateResult (request : FilesRequest) (result : Operation.OperationResult) = result |> checkRequestTypeHeader "Multipart" result.Data.IsSome |> equals true - result.Data.Value.UploadRequest.Single.ToDictionary() |> File.FromDictionary |> equals request.Single - result.Data.Value.UploadRequest.Multiple |> Array.map ((fun x -> x.ToDictionary()) >> File.FromDictionary) |> equals request.Multiple - result.Data.Value.UploadRequest.NullableMultiple |> Option.map (Array.map ((fun x -> x.ToDictionary()) >> File.FromDictionary)) |> equals request.NullableMultiple - result.Data.Value.UploadRequest.NullableMultipleNullable |> Option.map (Array.map (Option.map ((fun x -> x.ToDictionary()) >> File.FromDictionary))) |> equals request.NullableMultipleNullable + result.Data.Value.UploadRequest.Single.ToDictionary () + |> File.FromDictionary + |> equals request.Single + result.Data.Value.UploadRequest.Multiple + |> Array.map ((fun x -> x.ToDictionary ()) >> File.FromDictionary) + |> equals request.Multiple + result.Data.Value.UploadRequest.NullableMultiple + |> Option.map (Array.map ((fun x -> x.ToDictionary ()) >> File.FromDictionary)) + |> equals request.NullableMultiple + result.Data.Value.UploadRequest.NullableMultipleNullable + |> Option.map (Array.map (Option.map ((fun x -> x.ToDictionary ()) >> File.FromDictionary))) + |> equals request.NullableMultipleNullable [] -let ``Should be able to upload files inside another input type``() = - let request = - { Single = { Name = "single.txt"; ContentType = "text/plain"; Content = "Single file content" } - Multiple = - [| { Name = "multiple1.txt"; ContentType = "text/plain"; Content = "Multiple files first file content" } - { Name = "multiple2.txt"; ContentType = "text/plain"; Content = "Multiple files second file content" } |] - NullableMultiple = Some [| { Name = "multiple3.txt"; ContentType = "text/plain"; Content = "Multiple files third file content" } |] - NullableMultipleNullable = - Some [| Some { Name = "multiple4.txt"; ContentType = "text/plain"; Content = "Multiple files fourth file content" }; None |] } +let ``Should be able to upload files inside another input type`` () = + let request = { + Single = { + Name = "single.txt" + ContentType = "text/plain" + Content = "Single file content" + } + Multiple = [| + { + Name = "multiple1.txt" + ContentType = "text/plain" + Content = "Multiple files first file content" + } + { + Name = "multiple2.txt" + ContentType = "text/plain" + Content = "Multiple files second file content" + } + |] + NullableMultiple = + Some [| + { + Name = "multiple3.txt" + ContentType = "text/plain" + Content = "Multiple files third file content" + } + |] + NullableMultipleNullable = + Some [| + Some { + Name = "multiple4.txt" + ContentType = "text/plain" + Content = "Multiple files fourth file content" + } + None + |] + } let input = - let makeUpload (x : File) = x.MakeUpload() - UploadRequestOperation.Request(single = makeUpload request.Single, - multiple = Array.map makeUpload request.Multiple, - nullableMultiple = Some (Array.map makeUpload request.NullableMultiple.Value), - nullableMultipleNullable = Some (Array.map (Option.map makeUpload) request.NullableMultipleNullable.Value)) - UploadRequestOperation.operation.Run(input) + let makeUpload (x : File) = x.MakeUpload () + UploadRequestOperation.Request ( + single = makeUpload request.Single, + multiple = Array.map makeUpload request.Multiple, + nullableMultiple = Some (Array.map makeUpload request.NullableMultiple.Value), + nullableMultipleNullable = Some (Array.map (Option.map makeUpload) request.NullableMultipleNullable.Value) + ) + UploadRequestOperation.operation.Run (context, input) |> UploadRequestOperation.validateResult request module UploadComplexOperation = let operation = Provider.Operation<"""mutation UploadComplex($input: InputFile!) { uploadComplex(input: $input) - }""">() + }"""> () type Operation = Provider.Operations.UploadComplex type InputFile = Provider.Types.InputFile @@ -501,30 +672,46 @@ module UploadComplexOperation = [] let ``Should be able to upload file using complex input object`` () = - let file = { Name = "complex.txt"; ContentType = "text/plain"; Content = "Complex input object file content" } - let input = UploadComplexOperation.InputFile(file = file.MakeUpload()) - UploadComplexOperation.operation.Run(input) + let file = { + Name = "complex.txt" + ContentType = "text/plain" + Content = "Complex input object file content" + } + let input = UploadComplexOperation.InputFile (file = file.MakeUpload ()) + UploadComplexOperation.operation.Run (context, input) |> UploadComplexOperation.validateResult file [] let ``Should be able to upload file using complex input object with context`` () = - let file = { Name = "complex_context.txt"; ContentType = "text/plain"; Content = "Complex input with context file content" } - let input = UploadComplexOperation.InputFile(file = file.MakeUpload()) - UploadComplexOperation.operation.Run(context, input) + let file = { + Name = "complex_context.txt" + ContentType = "text/plain" + Content = "Complex input with context file content" + } + let input = UploadComplexOperation.InputFile (file = file.MakeUpload ()) + UploadComplexOperation.operation.Run (context, input) |> UploadComplexOperation.validateResult file [] let ``Should be able to upload file using complex input object asynchronously`` () : Task = task { - let file = { Name = "complex_async.txt"; ContentType = "text/plain"; Content = "Complex input object async file content" } - let input = UploadComplexOperation.InputFile(file = file.MakeUpload()) - let! result = UploadComplexOperation.operation.AsyncRun(input) + let file = { + Name = "complex_async.txt" + ContentType = "text/plain" + Content = "Complex input object async file content" + } + let input = UploadComplexOperation.InputFile (file = file.MakeUpload ()) + let! result = UploadComplexOperation.operation.AsyncRun (context, input) result |> UploadComplexOperation.validateResult file } [] let ``Should be able to upload file using complex input object with context asynchronously`` () : Task = task { - let file = { Name = "complex_context_async.txt"; ContentType = "text/plain"; Content = "Complex input with context async file content" } - let input = UploadComplexOperation.InputFile(file = file.MakeUpload()) - let! result = UploadComplexOperation.operation.AsyncRun(context, input) + let file = { + Name = "complex_context_async.txt" + ContentType = "text/plain" + Content = "Complex input with context async file content" + } + let input = UploadComplexOperation.InputFile (file = file.MakeUpload ()) + let! result = UploadComplexOperation.operation.AsyncRun (context, input) result |> UploadComplexOperation.validateResult file } diff --git a/tests/FSharp.Data.GraphQL.IntegrationTests/OperationErrorTests.fs b/tests/FSharp.Data.GraphQL.IntegrationTests/OperationErrorTests.fs index f95b44df0..5080dc195 100644 --- a/tests/FSharp.Data.GraphQL.IntegrationTests/OperationErrorTests.fs +++ b/tests/FSharp.Data.GraphQL.IntegrationTests/OperationErrorTests.fs @@ -7,9 +7,13 @@ open FSharp.Data.GraphQL open FSharp.Data.GraphQL.Client [] -let ServerUrl = "http://localhost:8085" +let IntrospectionPath = "integration-introspection.json" -type Provider = GraphQLProvider +type Provider = GraphQLProvider + +let connection = TestHosts.createIntegrationConnection () +let context = + Provider.GetContext (serverUrl = TestHosts.integrationServerUrl, connectionFactory = (fun () -> connection)) module ErrorOperation = let operation = @@ -106,7 +110,7 @@ let ``Should parse all combinations of optional operation error fields`` () = [] let ``Should map server error extensions and locations into operation result`` () = - let result = ErrorOperation.operation.Run () + let result = ErrorOperation.operation.Run (context) result.Errors.Length |> equals 1 diff --git a/tests/FSharp.Data.GraphQL.IntegrationTests/ReservedScalarNameProviderTests.fs b/tests/FSharp.Data.GraphQL.IntegrationTests/ReservedScalarNameProviderTests.fs index 3a938226b..0d3df7de9 100644 --- a/tests/FSharp.Data.GraphQL.IntegrationTests/ReservedScalarNameProviderTests.fs +++ b/tests/FSharp.Data.GraphQL.IntegrationTests/ReservedScalarNameProviderTests.fs @@ -15,10 +15,10 @@ module ObjectDateSchema = value category } - }""">() + }"""> () let compileSmoke () = - let schemaDate = SchemaDate(value = "2026-04-03", category = "default") + let schemaDate = SchemaDate (value = "2026-04-03", category = "default") let operationInstance : ObjectDateProvider.Operations.Q = operation schemaDate |> ignore operationInstance |> ignore @@ -29,19 +29,17 @@ module InputDateSchema = let operation = InputDateProvider.Operation<"""query Q($input: Date) { echoDate(input: $input) - }""">() + }"""> () let compileSmoke () = - let schemaDate = SchemaDate(value = "2026-04-03", category = "default") + let schemaDate = SchemaDate (value = "2026-04-03", category = "default") let deferredRun : unit -> _ = - fun () -> operation.Run(Unchecked.defaultof, schemaDate) + fun () -> operation.Run (Unchecked.defaultof, schemaDate) schemaDate |> ignore deferredRun |> ignore [] -let ``Should allow object types that reuse reserved scalar names`` () = - ObjectDateSchema.compileSmoke () +let ``Should allow object types that reuse reserved scalar names`` () = ObjectDateSchema.compileSmoke () [] -let ``Should allow input object types that reuse reserved scalar names`` () = - InputDateSchema.compileSmoke () +let ``Should allow input object types that reuse reserved scalar names`` () = InputDateSchema.compileSmoke () diff --git a/tests/FSharp.Data.GraphQL.IntegrationTests/SwapiLocalProviderTests.fs b/tests/FSharp.Data.GraphQL.IntegrationTests/SwapiLocalProviderTests.fs index e673cba63..892f1636c 100644 --- a/tests/FSharp.Data.GraphQL.IntegrationTests/SwapiLocalProviderTests.fs +++ b/tests/FSharp.Data.GraphQL.IntegrationTests/SwapiLocalProviderTests.fs @@ -3,17 +3,17 @@ module FSharp.Data.GraphQL.IntegrationTests.SwapiLocalProviderTests open Xunit open Helpers open FSharp.Data.GraphQL -open System.Net.Http open System.Threading.Tasks // Local provider should be able to be created from local introspection json file. type Provider = GraphQLProvider<"introspection.json"> // We are going to re-use the same HttpClient through all requests. -let connection = new GraphQLClientConnection(new HttpClient()) +let connection = TestHosts.createStarWarsConnection () // As we are not using a connection to a server to get the introspection, we need a runtime context. -let getContext() = Provider.GetContext(serverUrl = "http://localhost:8086", connectionFactory = fun () -> connection) +let getContext () = + Provider.GetContext (serverUrl = TestHosts.starWarsServerUrl, connectionFactory = (fun () -> connection)) type Episode = Provider.Types.Episode @@ -45,26 +45,33 @@ hero (id: "1000") { } } } - }""">() + }"""> () type Operation = Provider.Operations.Q let validateResult (result : Operation.OperationResult) = - result.CustomData.ContainsKey("documentId") |> equals true + result.CustomData.ContainsKey ("documentId") |> equals true result.Errors |> equals [||] result.Data.IsSome |> equals true result.Data.Value.Hero.IsSome |> equals true - result.Data.Value.Hero.Value.AppearsIn |> equals [| Episode.NewHope; Episode.Empire; Episode.Jedi |] - let expectedFriends : Operation.Types.HeroFields.FriendsFields.EdgesFields.NodeFields.Character array = - [| Operation.Types.HeroFields.FriendsFields.EdgesFields.NodeFields.Human(name = "Han Solo") - Operation.Types.HeroFields.FriendsFields.EdgesFields.NodeFields.Human(name = "Leia Organa", homePlanet = "Alderaan") - Operation.Types.HeroFields.FriendsFields.EdgesFields.NodeFields.Droid(name = "C-3PO", primaryFunction = "Protocol") - Operation.Types.HeroFields.FriendsFields.EdgesFields.NodeFields.Droid(name = "R2-D2", primaryFunction = "Astromech") |] - let friends = result.Data.Value.Hero.Value.Friends.Edges |> Array.map (fun x -> x.Node) + result.Data.Value.Hero.Value.AppearsIn + |> equals [| Episode.NewHope; Episode.Empire; Episode.Jedi |] + let expectedFriends : Operation.Types.HeroFields.FriendsFields.EdgesFields.NodeFields.Character array = [| + Operation.Types.HeroFields.FriendsFields.EdgesFields.NodeFields.Human (name = "Han Solo") + Operation.Types.HeroFields.FriendsFields.EdgesFields.NodeFields.Human (name = "Leia Organa", homePlanet = "Alderaan") + Operation.Types.HeroFields.FriendsFields.EdgesFields.NodeFields.Droid (name = "C-3PO", primaryFunction = "Protocol") + Operation.Types.HeroFields.FriendsFields.EdgesFields.NodeFields.Droid (name = "R2-D2", primaryFunction = "Astromech") + |] + let friends = + result.Data.Value.Hero.Value.Friends.Edges + |> Array.map (fun x -> x.Node) friends |> equals expectedFriends - result.Data.Value.Hero.Value.HomePlanet |> equals (Some "Tatooine") + result.Data.Value.Hero.Value.HomePlanet + |> equals (Some "Tatooine") let actual = normalize <| sprintf "%A" result.Data - let expected = normalize <| """Some + let expected = + normalize + <| """Some {Hero = Some {AppearsIn = [|NewHope; Empire; Jedi|]; Friends = {Edges = [|{Cursor = "RnJpZW5kOjEwMDI="; @@ -88,54 +95,62 @@ hero (id: "1000") { [] let ``Should be able to start a simple query operation synchronously`` () = - use context = getContext() - SimpleOperation.operation.Run(context) + use context = getContext () + SimpleOperation.operation.Run (context) |> SimpleOperation.validateResult [] let ``Should be able to start a simple query operation asynchronously`` () : Task = task { - use context = getContext() - let! result = SimpleOperation.operation.AsyncRun(context) + use context = getContext () + let! result = SimpleOperation.operation.AsyncRun (context) result |> SimpleOperation.validateResult } [] let ``Should be able to use pattern matching methods on an union type`` () = - use context = getContext() - let result = SimpleOperation.operation.Run(context) + use context = getContext () + let result = SimpleOperation.operation.Run (context) result.Data.IsSome |> equals true result.Data.Value.Hero.IsSome |> equals true - let friends = result.Data.Value.Hero.Value.Friends.Edges |> Array.map (fun x -> x.Node) + let friends = + result.Data.Value.Hero.Value.Friends.Edges + |> Array.map (fun x -> x.Node) friends - |> Array.choose (fun x -> x.TryAsHuman()) + |> Array.choose (fun x -> x.TryAsHuman ()) |> equals [| - SimpleOperation.Operation.Types.HeroFields.FriendsFields.EdgesFields.NodeFields.Human(name = "Han Solo") - SimpleOperation.Operation.Types.HeroFields.FriendsFields.EdgesFields.NodeFields.Human(name = "Leia Organa", homePlanet = "Alderaan") |] + SimpleOperation.Operation.Types.HeroFields.FriendsFields.EdgesFields.NodeFields.Human (name = "Han Solo") + SimpleOperation.Operation.Types.HeroFields.FriendsFields.EdgesFields.NodeFields.Human (name = "Leia Organa", homePlanet = "Alderaan") + |] friends - |> Array.choose (fun x -> x.TryAsDroid()) + |> Array.choose (fun x -> x.TryAsDroid ()) |> equals [| - SimpleOperation.Operation.Types.HeroFields.FriendsFields.EdgesFields.NodeFields.Droid(name = "C-3PO", primaryFunction = "Protocol") - SimpleOperation.Operation.Types.HeroFields.FriendsFields.EdgesFields.NodeFields.Droid(name = "R2-D2", primaryFunction = "Astromech") |] + SimpleOperation.Operation.Types.HeroFields.FriendsFields.EdgesFields.NodeFields.Droid (name = "C-3PO", primaryFunction = "Protocol") + SimpleOperation.Operation.Types.HeroFields.FriendsFields.EdgesFields.NodeFields.Droid (name = "R2-D2", primaryFunction = "Astromech") + |] try - friends |> Array.map (fun x -> x.AsDroid()) |> ignore - failwith "Expected exception when trying to get all friends as droids!" - with _ -> () + friends |> Array.map (fun x -> x.AsDroid ()) |> ignore + failwith "Expected exception when trying to get all friends as droids!" + with _ -> + () try - friends |> Array.map (fun x -> x.AsHuman()) |> ignore - failwith "Expected exception when trying to get all friends as humans!" - with _ -> () + friends |> Array.map (fun x -> x.AsHuman ()) |> ignore + failwith "Expected exception when trying to get all friends as humans!" + with _ -> + () friends - |> Array.filter (fun x -> x.IsHuman()) - |> Array.map (fun x -> x.AsHuman()) + |> Array.filter (fun x -> x.IsHuman ()) + |> Array.map (fun x -> x.AsHuman ()) |> equals [| - SimpleOperation.Operation.Types.HeroFields.FriendsFields.EdgesFields.NodeFields.Human(name = "Han Solo") - SimpleOperation.Operation.Types.HeroFields.FriendsFields.EdgesFields.NodeFields.Human(name = "Leia Organa", homePlanet = "Alderaan") |] + SimpleOperation.Operation.Types.HeroFields.FriendsFields.EdgesFields.NodeFields.Human (name = "Han Solo") + SimpleOperation.Operation.Types.HeroFields.FriendsFields.EdgesFields.NodeFields.Human (name = "Leia Organa", homePlanet = "Alderaan") + |] friends - |> Array.filter (fun x -> x.IsDroid()) - |> Array.map (fun x -> x.AsDroid()) + |> Array.filter (fun x -> x.IsDroid ()) + |> Array.map (fun x -> x.AsDroid ()) |> equals [| - SimpleOperation.Operation.Types.HeroFields.FriendsFields.EdgesFields.NodeFields.Droid(name = "C-3PO", primaryFunction = "Protocol") - SimpleOperation.Operation.Types.HeroFields.FriendsFields.EdgesFields.NodeFields.Droid(name = "R2-D2", primaryFunction = "Astromech") |] + SimpleOperation.Operation.Types.HeroFields.FriendsFields.EdgesFields.NodeFields.Droid (name = "C-3PO", primaryFunction = "Protocol") + SimpleOperation.Operation.Types.HeroFields.FriendsFields.EdgesFields.NodeFields.Droid (name = "R2-D2", primaryFunction = "Astromech") + |] module MutationOperation = let operation = @@ -145,52 +160,60 @@ module MutationOperation = name isMoon } - }""">() + }"""> () type Operation = Provider.Operations.M let validateResult (result : Operation.OperationResult) = - result.CustomData.ContainsKey("documentId") |> equals true + result.CustomData.ContainsKey ("documentId") |> equals true result.Errors |> equals [||] result.Data.IsSome |> equals true result.Data.Value.SetMoon.IsSome |> equals true result.Data.Value.SetMoon.Value.Id |> equals "1" - result.Data.Value.SetMoon.Value.Name |> equals (Some "Tatooine") + result.Data.Value.SetMoon.Value.Name + |> equals (Some "Tatooine") result.Data.Value.SetMoon.Value.IsMoon |> equals (Some true) [] let ``Should be able to run a mutation synchronously`` () = - use context = getContext() - MutationOperation.operation.Run(context) + use context = getContext () + MutationOperation.operation.Run (context) |> MutationOperation.validateResult [] let ``Should be able to run a mutation asynchronously`` () : Task = task { - use context = getContext() - let! result = MutationOperation.operation.AsyncRun(context) + use context = getContext () + let! result = MutationOperation.operation.AsyncRun (context) result |> MutationOperation.validateResult } module FileOperation = - let fileop = Provider.Operation<"operation.graphql">() + let fileop = Provider.Operation<"operation.graphql"> () type Operation = Provider.Operations.FileOp let validateResult (result : Operation.OperationResult) = - result.CustomData.ContainsKey("documentId") |> equals true + result.CustomData.ContainsKey ("documentId") |> equals true result.Errors |> equals [||] result.Data.IsSome |> equals true result.Data.Value.Hero.IsSome |> equals true - result.Data.Value.Hero.Value.AppearsIn |> equals [| Episode.NewHope; Episode.Empire; Episode.Jedi |] - let expectedFriends : Operation.Types.HeroFields.FriendsFields.EdgesFields.NodeFields.Character array = - [| Operation.Types.HeroFields.FriendsFields.EdgesFields.NodeFields.Human(name = "Han Solo") - Operation.Types.HeroFields.FriendsFields.EdgesFields.NodeFields.Human(name = "Leia Organa", homePlanet = "Alderaan") - Operation.Types.HeroFields.FriendsFields.EdgesFields.NodeFields.Droid(name = "C-3PO", primaryFunction = "Protocol") - Operation.Types.HeroFields.FriendsFields.EdgesFields.NodeFields.Droid(name = "R2-D2", primaryFunction = "Astromech") |] - let friends = result.Data.Value.Hero.Value.Friends.Edges |> Array.map (fun x -> x.Node) + result.Data.Value.Hero.Value.AppearsIn + |> equals [| Episode.NewHope; Episode.Empire; Episode.Jedi |] + let expectedFriends : Operation.Types.HeroFields.FriendsFields.EdgesFields.NodeFields.Character array = [| + Operation.Types.HeroFields.FriendsFields.EdgesFields.NodeFields.Human (name = "Han Solo") + Operation.Types.HeroFields.FriendsFields.EdgesFields.NodeFields.Human (name = "Leia Organa", homePlanet = "Alderaan") + Operation.Types.HeroFields.FriendsFields.EdgesFields.NodeFields.Droid (name = "C-3PO", primaryFunction = "Protocol") + Operation.Types.HeroFields.FriendsFields.EdgesFields.NodeFields.Droid (name = "R2-D2", primaryFunction = "Astromech") + |] + let friends = + result.Data.Value.Hero.Value.Friends.Edges + |> Array.map (fun x -> x.Node) friends |> equals expectedFriends - result.Data.Value.Hero.Value.HomePlanet |> equals (Some "Tatooine") + result.Data.Value.Hero.Value.HomePlanet + |> equals (Some "Tatooine") let actual = normalize <| sprintf "%A" result.Data - let expected = normalize <| """Some + let expected = + normalize + <| """Some {Hero = Some {AppearsIn = [|NewHope; Empire; Jedi|]; Friends = {Edges = [|{Cursor = "RnJpZW5kOjEwMDI="; @@ -214,6 +237,6 @@ module FileOperation = [] let ``Should be able to run a query from a query file`` () = - use context = getContext() - FileOperation.fileop.Run(context) + use context = getContext () + FileOperation.fileop.Run (context) |> FileOperation.validateResult diff --git a/tests/FSharp.Data.GraphQL.IntegrationTests/SwapiRemoteProviderTests.fs b/tests/FSharp.Data.GraphQL.IntegrationTests/SwapiRemoteProviderTests.fs index dc73b38db..bc92cb8a7 100644 --- a/tests/FSharp.Data.GraphQL.IntegrationTests/SwapiRemoteProviderTests.fs +++ b/tests/FSharp.Data.GraphQL.IntegrationTests/SwapiRemoteProviderTests.fs @@ -5,7 +5,11 @@ open Helpers open FSharp.Data.GraphQL open System.Threading.Tasks -type Provider = GraphQLProvider<"http://localhost:8086"> +type Provider = GraphQLProvider<"introspection.json"> + +let connection = TestHosts.createStarWarsConnection () +let context = + Provider.GetContext (serverUrl = TestHosts.starWarsServerUrl, connectionFactory = (fun () -> connection)) type Episode = Provider.Types.Episode @@ -37,26 +41,48 @@ hero (id: "1000") { } } } - }""">() + }"""> () type Operation = Provider.Operations.Q let validateResult (result : Operation.OperationResult) = - result.CustomData.ContainsKey("documentId") |> equals true + result.CustomData.ContainsKey ("documentId") |> equals true result.Errors |> equals [||] result.Data.IsSome |> equals true result.Data.Value.Hero.IsSome |> equals true - result.Data.Value.Hero.Value.AppearsIn |> equals [| Episode.NewHope; Episode.Empire; Episode.Jedi |] - let expectedFriends : Operation.Types.HeroFields.FriendsFields.EdgesFields.NodeFields.Character array = - [| Operation.Types.HeroFields.FriendsFields.EdgesFields.NodeFields.Human(name = "Han Solo") - Operation.Types.HeroFields.FriendsFields.EdgesFields.NodeFields.Human(name = "Leia Organa", homePlanet = "Alderaan") - Operation.Types.HeroFields.FriendsFields.EdgesFields.NodeFields.Droid(name = "C-3PO", primaryFunction = "Protocol") - Operation.Types.HeroFields.FriendsFields.EdgesFields.NodeFields.Droid(name = "R2-D2", primaryFunction = "Astromech") |] - let friends = result.Data.Value.Hero.Value.Friends.Edges |> Array.map (fun e -> e.Node) - friends |> equals expectedFriends - result.Data.Value.Hero.Value.HomePlanet |> equals (Some "Tatooine") + result.Data.Value.Hero.Value.AppearsIn + |> equals [| Episode.NewHope; Episode.Empire; Episode.Jedi |] + let friends = + result.Data.Value.Hero.Value.Friends.Edges + |> Array.map (fun e -> e.Node) + friends.Length |> equals 4 + do + let friend0 = friends[0] + friend0.IsHuman () |> equals true + friend0.AsHuman().Name |> equals (Some "Han Solo") + do + let friend1 = friends[1] + friend1.IsHuman () |> equals true + friend1.AsHuman().Name |> equals (Some "Leia Organa") + friend1.AsHuman().HomePlanet |> equals (Some "Alderaan") + do + let friend2 = friends[2] + friend2.IsDroid () |> equals true + friend2.AsDroid().Name |> equals (Some "C-3PO") + friend2.AsDroid().PrimaryFunction + |> equals (Some "Protocol") + do + let friend3 = friends[3] + friend3.IsDroid () |> equals true + friend3.AsDroid().Name |> equals (Some "R2-D2") + friend3.AsDroid().PrimaryFunction + |> equals (Some "Astromech") + result.Data.Value.Hero.Value.HomePlanet + |> equals (Some "Tatooine") let actual = normalize <| sprintf "%A" result.Data - let expected = normalize <| """Some + let expected = + normalize + <| """Some {Hero = Some {AppearsIn = [|NewHope; Empire; Jedi|]; Friends = {Edges = [|{Cursor = "RnJpZW5kOjEwMDI="; @@ -80,51 +106,59 @@ hero (id: "1000") { [] let ``Should be able to start a simple query operation synchronously`` () = - SimpleOperation.operation.Run() + SimpleOperation.operation.Run (context) |> SimpleOperation.validateResult [] let ``Should be able to start a simple query operation asynchronously`` () : Task = task { - let! result = SimpleOperation.operation.AsyncRun() + let! result = SimpleOperation.operation.AsyncRun (context) result |> SimpleOperation.validateResult } [] let ``Should be able to use pattern matching methods on an union type`` () = - let result = SimpleOperation.operation.Run() + let result = SimpleOperation.operation.Run (context) result.Data.IsSome |> equals true result.Data.Value.Hero.IsSome |> equals true - let friends = result.Data.Value.Hero.Value.Friends.Edges |> Array.map (fun e -> e.Node) + let friends = + result.Data.Value.Hero.Value.Friends.Edges + |> Array.map (fun e -> e.Node) friends - |> Array.choose (fun x -> x.TryAsHuman()) + |> Array.choose (fun x -> x.TryAsHuman ()) |> equals [| - SimpleOperation.Operation.Types.HeroFields.FriendsFields.EdgesFields.NodeFields.Human(name = "Han Solo") - SimpleOperation.Operation.Types.HeroFields.FriendsFields.EdgesFields.NodeFields.Human(name = "Leia Organa", homePlanet = "Alderaan") |] + SimpleOperation.Operation.Types.HeroFields.FriendsFields.EdgesFields.NodeFields.Human (name = "Han Solo") + SimpleOperation.Operation.Types.HeroFields.FriendsFields.EdgesFields.NodeFields.Human (name = "Leia Organa", homePlanet = "Alderaan") + |] friends - |> Array.choose (fun x -> x.TryAsDroid()) + |> Array.choose (fun x -> x.TryAsDroid ()) |> equals [| - SimpleOperation.Operation.Types.HeroFields.FriendsFields.EdgesFields.NodeFields.Droid(name = "C-3PO", primaryFunction = "Protocol") - SimpleOperation.Operation.Types.HeroFields.FriendsFields.EdgesFields.NodeFields.Droid(name = "R2-D2", primaryFunction = "Astromech") |] + SimpleOperation.Operation.Types.HeroFields.FriendsFields.EdgesFields.NodeFields.Droid (name = "C-3PO", primaryFunction = "Protocol") + SimpleOperation.Operation.Types.HeroFields.FriendsFields.EdgesFields.NodeFields.Droid (name = "R2-D2", primaryFunction = "Astromech") + |] try - friends |> Array.map (fun x -> x.AsDroid()) |> ignore - failwith "Expected exception when trying to get all friends as droids!" - with _ -> () + friends |> Array.map (fun x -> x.AsDroid ()) |> ignore + failwith "Expected exception when trying to get all friends as droids!" + with _ -> + () try - friends |> Array.map (fun x -> x.AsHuman()) |> ignore - failwith "Expected exception when trying to get all friends as humans!" - with _ -> () + friends |> Array.map (fun x -> x.AsHuman ()) |> ignore + failwith "Expected exception when trying to get all friends as humans!" + with _ -> + () friends - |> Array.filter (fun x -> x.IsHuman()) - |> Array.map (fun x -> x.AsHuman()) + |> Array.filter (fun x -> x.IsHuman ()) + |> Array.map (fun x -> x.AsHuman ()) |> equals [| - SimpleOperation.Operation.Types.HeroFields.FriendsFields.EdgesFields.NodeFields.Human(name = "Han Solo") - SimpleOperation.Operation.Types.HeroFields.FriendsFields.EdgesFields.NodeFields.Human(name = "Leia Organa", homePlanet = "Alderaan") |] + SimpleOperation.Operation.Types.HeroFields.FriendsFields.EdgesFields.NodeFields.Human (name = "Han Solo") + SimpleOperation.Operation.Types.HeroFields.FriendsFields.EdgesFields.NodeFields.Human (name = "Leia Organa", homePlanet = "Alderaan") + |] friends - |> Array.filter (fun x -> x.IsDroid()) - |> Array.map (fun x -> x.AsDroid()) + |> Array.filter (fun x -> x.IsDroid ()) + |> Array.map (fun x -> x.AsDroid ()) |> equals [| - SimpleOperation.Operation.Types.HeroFields.FriendsFields.EdgesFields.NodeFields.Droid(name = "C-3PO", primaryFunction = "Protocol") - SimpleOperation.Operation.Types.HeroFields.FriendsFields.EdgesFields.NodeFields.Droid(name = "R2-D2", primaryFunction = "Astromech") |] + SimpleOperation.Operation.Types.HeroFields.FriendsFields.EdgesFields.NodeFields.Droid (name = "C-3PO", primaryFunction = "Protocol") + SimpleOperation.Operation.Types.HeroFields.FriendsFields.EdgesFields.NodeFields.Droid (name = "R2-D2", primaryFunction = "Astromech") + |] module MutationOperation = let operation = @@ -134,51 +168,74 @@ module MutationOperation = name isMoon } - }""">() + }"""> () type Operation = Provider.Operations.M let validateResult (result : Operation.OperationResult) = - result.CustomData.ContainsKey("documentId") |> equals true + result.CustomData.ContainsKey ("documentId") |> equals true result.Errors |> equals [||] result.Data.IsSome |> equals true result.Data.Value.SetMoon.IsSome |> equals true result.Data.Value.SetMoon.Value.Id |> equals "1" - result.Data.Value.SetMoon.Value.Name |> equals (Some "Tatooine") + result.Data.Value.SetMoon.Value.Name + |> equals (Some "Tatooine") result.Data.Value.SetMoon.Value.IsMoon |> equals (Some true) [] let ``Should be able to run a mutation synchronously`` () = - MutationOperation.operation.Run() + MutationOperation.operation.Run (context) |> MutationOperation.validateResult [] let ``Should be able to run a mutation asynchronously`` () : Task = task { - let! result = MutationOperation.operation.AsyncRun() + let! result = MutationOperation.operation.AsyncRun (context) result |> MutationOperation.validateResult } module FileOperation = - let fileOp = Provider.Operation<"operation.graphql">() + let fileOp = Provider.Operation<"operation.graphql"> () type Operation = Provider.Operations.FileOp let validateResult (result : Operation.OperationResult) = - result.CustomData.ContainsKey("documentId") |> equals true + result.CustomData.ContainsKey ("documentId") |> equals true result.Errors |> equals [||] result.Data.IsSome |> equals true result.Data.Value.Hero.IsSome |> equals true - result.Data.Value.Hero.Value.AppearsIn |> equals [| Episode.NewHope; Episode.Empire; Episode.Jedi |] - let expectedFriends : Operation.Types.HeroFields.FriendsFields.EdgesFields.NodeFields.Character array = - [| Operation.Types.HeroFields.FriendsFields.EdgesFields.NodeFields.Human(name = "Han Solo") - Operation.Types.HeroFields.FriendsFields.EdgesFields.NodeFields.Human(name = "Leia Organa", homePlanet = "Alderaan") - Operation.Types.HeroFields.FriendsFields.EdgesFields.NodeFields.Droid(name = "C-3PO", primaryFunction = "Protocol") - Operation.Types.HeroFields.FriendsFields.EdgesFields.NodeFields.Droid(name = "R2-D2", primaryFunction = "Astromech") |] - let friends = result.Data.Value.Hero.Value.Friends.Edges |> Array.map _.Node - friends |> equals expectedFriends - result.Data.Value.Hero.Value.HomePlanet |> equals (Some "Tatooine") + result.Data.Value.Hero.Value.AppearsIn + |> equals [| Episode.NewHope; Episode.Empire; Episode.Jedi |] + let friends = + result.Data.Value.Hero.Value.Friends.Edges + |> Array.map _.Node + friends.Length |> equals 4 + do + let friend0 = friends[0] + friend0.IsHuman () |> equals true + friend0.AsHuman().Name |> equals (Some "Han Solo") + do + let friend1 = friends[1] + friend1.IsHuman () |> equals true + friend1.AsHuman().Name |> equals (Some "Leia Organa") + friend1.AsHuman().HomePlanet |> equals (Some "Alderaan") + do + let friend2 = friends[2] + friend2.IsDroid () |> equals true + friend2.AsDroid().Name |> equals (Some "C-3PO") + friend2.AsDroid().PrimaryFunction + |> equals (Some "Protocol") + do + let friend3 = friends[3] + friend3.IsDroid () |> equals true + friend3.AsDroid().Name |> equals (Some "R2-D2") + friend3.AsDroid().PrimaryFunction + |> equals (Some "Astromech") + result.Data.Value.Hero.Value.HomePlanet + |> equals (Some "Tatooine") let actual = normalize <| sprintf "%A" result.Data - let expected = normalize <| """Some + let expected = + normalize + <| """Some {Hero = Some {AppearsIn = [|NewHope; Empire; Jedi|]; Friends = {Edges = [|{Cursor = "RnJpZW5kOjEwMDI="; @@ -202,5 +259,5 @@ module FileOperation = [] let ``Should be able to run a query from a query file`` () = - FileOperation.fileOp.Run() + FileOperation.fileOp.Run (context) |> FileOperation.validateResult diff --git a/tests/FSharp.Data.GraphQL.IntegrationTests/TestHosts.fs b/tests/FSharp.Data.GraphQL.IntegrationTests/TestHosts.fs new file mode 100644 index 000000000..c2faa8a00 --- /dev/null +++ b/tests/FSharp.Data.GraphQL.IntegrationTests/TestHosts.fs @@ -0,0 +1,41 @@ +module FSharp.Data.GraphQL.IntegrationTests.TestHosts + +open FSharp.Data.GraphQL +open Microsoft.AspNetCore.Mvc.Testing +open System.Net.Http +open System + +type IntegrationServerApplicationFactory () = + inherit WebApplicationFactory () + +type StarWarsApplicationFactory () = + inherit WebApplicationFactory () + +let private integrationFactory = lazy (new IntegrationServerApplicationFactory ()) +let private starWarsFactory = lazy (new StarWarsApplicationFactory ()) + +let createIntegrationHttpClient () : HttpClient = integrationFactory.Value.CreateClient () + +let createStarWarsHttpClient () : HttpClient = starWarsFactory.Value.CreateClient () + +let private getIntegrationServerUrl () = + use client = createIntegrationHttpClient () + client.BaseAddress.ToString().TrimEnd '/' + +let private getStarWarsServerUrl () = + use client = createStarWarsHttpClient () + client.BaseAddress.ToString().TrimEnd '/' + +do + AppDomain.CurrentDomain.ProcessExit.Add (fun _ -> + if integrationFactory.IsValueCreated then + integrationFactory.Value.Dispose () + + if starWarsFactory.IsValueCreated then + starWarsFactory.Value.Dispose ()) + +let integrationServerUrl = getIntegrationServerUrl () +let starWarsServerUrl = getStarWarsServerUrl () + +let createIntegrationConnection () = new GraphQLClientConnection (createIntegrationHttpClient ()) +let createStarWarsConnection () = new GraphQLClientConnection (createStarWarsHttpClient ()) diff --git a/tests/FSharp.Data.GraphQL.IntegrationTests/integration-introspection.json b/tests/FSharp.Data.GraphQL.IntegrationTests/integration-introspection.json new file mode 100644 index 000000000..ad448c42f --- /dev/null +++ b/tests/FSharp.Data.GraphQL.IntegrationTests/integration-introspection.json @@ -0,0 +1,1929 @@ +{ + "documentId": 986164407, + "data": { + "__schema": { + "queryType": { + "name": "Query" + }, + "mutationType": { + "name": "Mutation" + }, + "subscriptionType": null, + "types": [ + { + "kind": "SCALAR", + "name": "Int", + "description": "The \u0060Int\u0060 scalar type represents non-fractional signed whole numeric values. Int can represent values between -(2^31) and 2^31 - 1.", + "fields": null, + "inputFields": null, + "interfaces": null, + "enumValues": null, + "possibleTypes": null + }, + { + "kind": "SCALAR", + "name": "String", + "description": "The \u0060String\u0060 scalar type represents textual data, represented as UTF-8 character sequences. The \u0060String\u0060 type is most often used by GraphQL to represent free-form human-readable text.", + "fields": null, + "inputFields": null, + "interfaces": null, + "enumValues": null, + "possibleTypes": null + }, + { + "kind": "SCALAR", + "name": "Boolean", + "description": "The \u0060Boolean\u0060 scalar type represents \u0060true\u0060 or \u0060false\u0060.", + "fields": null, + "inputFields": null, + "interfaces": null, + "enumValues": null, + "possibleTypes": null + }, + { + "kind": "SCALAR", + "name": "Float", + "description": "The \u0060Float\u0060 scalar type represents signed double-precision fractional values as specified by [IEEE 754](http://en.wikipedia.org/wiki/IEEE_floating_point).", + "fields": null, + "inputFields": null, + "interfaces": null, + "enumValues": null, + "possibleTypes": null + }, + { + "kind": "SCALAR", + "name": "ID", + "description": "The \u0060ID\u0060 scalar type represents a unique identifier, often used to refetch an object or as key for a cache. The \u0060ID\u0060 type appears in a JSON response as a String; however, it is not intended to be human-readable. When expected as an input type, any string (such as \u0060\u00224\u0022\u0060) or integer (such as \u00604\u0060) input value will be accepted as an ID.", + "fields": null, + "inputFields": null, + "interfaces": null, + "enumValues": null, + "possibleTypes": null + }, + { + "kind": "SCALAR", + "name": "DateTimeOffset", + "description": "The \u0060DateTimeOffset\u0060 scalar type represents a Date value with Time component. The \u0060DateTimeOffset\u0060 type appears in a JSON response as a String representation compatible with ISO-8601 format.", + "fields": null, + "inputFields": null, + "interfaces": null, + "enumValues": null, + "possibleTypes": null + }, + { + "kind": "SCALAR", + "name": "DateOnly", + "description": "The \u0060DateOnly\u0060 scalar type represents a Date value without Time component. The \u0060DateOnly\u0060 type appears in a JSON response as a \u0060String\u0060 representation of full-date value as specified by [IETF 3339](https://www.ietf.org/rfc/rfc3339.txt).", + "fields": null, + "inputFields": null, + "interfaces": null, + "enumValues": null, + "possibleTypes": null + }, + { + "kind": "SCALAR", + "name": "TimeOnly", + "description": "The \u0060TimeOnly\u0060 scalar type represents a Time value without Date component. The \u0060TimeOnly\u0060 type appears in a JSON response as a \u0060String\u0060 representation of full-time value as specified by [IETF 3339](https://www.ietf.org/rfc/rfc3339.txt).", + "fields": null, + "inputFields": null, + "interfaces": null, + "enumValues": null, + "possibleTypes": null + }, + { + "kind": "SCALAR", + "name": "URI", + "description": "The \u0060URI\u0060 scalar type represents a string resource identifier compatible with URI standard. The \u0060URI\u0060 type appears in a JSON response as a String.", + "fields": null, + "inputFields": null, + "interfaces": null, + "enumValues": null, + "possibleTypes": null + }, + { + "kind": "OBJECT", + "name": "__Schema", + "description": "A GraphQL Schema defines the capabilities of a GraphQL server. It exposes all available types and directives on the server, as well as the entry points for query, mutation, and subscription operations.", + "fields": [ + { + "name": "directives", + "description": "A list of all directives supported by this server.", + "args": [], + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "LIST", + "name": null, + "ofType": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "OBJECT", + "name": "__Directive", + "ofType": null + } + } + } + }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "mutationType", + "description": "If this server supports mutation, the type that mutation operations will be rooted at.", + "args": [], + "type": { + "kind": "OBJECT", + "name": "__Type", + "ofType": null + }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "queryType", + "description": "The type that query operations will be rooted at.", + "args": [], + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "OBJECT", + "name": "__Type", + "ofType": null + } + }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "subscriptionType", + "description": "If this server support subscription, the type that subscription operations will be rooted at.", + "args": [], + "type": { + "kind": "OBJECT", + "name": "__Type", + "ofType": null + }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "types", + "description": "A list of all types supported by this server.", + "args": [], + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "LIST", + "name": null, + "ofType": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "OBJECT", + "name": "__Type", + "ofType": null + } + } + } + }, + "isDeprecated": false, + "deprecationReason": null + } + ], + "inputFields": null, + "interfaces": [], + "enumValues": null, + "possibleTypes": null + }, + { + "kind": "OBJECT", + "name": "__Directive", + "description": "A Directive provides a way to describe alternate runtime execution and type validation behavior in a GraphQL document. In some cases, you need to provide options to alter GraphQL\u2019s execution behavior in ways field arguments will not suffice, such as conditionally including or skipping a field. Directives provide this by describing additional information to the executor.", + "fields": [ + { + "name": "args", + "description": null, + "args": [], + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "LIST", + "name": null, + "ofType": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "OBJECT", + "name": "__InputValue", + "ofType": null + } + } + } + }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "description", + "description": null, + "args": [], + "type": { + "kind": "SCALAR", + "name": "String", + "ofType": null + }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "locations", + "description": null, + "args": [], + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "LIST", + "name": null, + "ofType": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "ENUM", + "name": "__DirectiveLocation", + "ofType": null + } + } + } + }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "name", + "description": null, + "args": [], + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "SCALAR", + "name": "String", + "ofType": null + } + }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "onField", + "description": null, + "args": [], + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "SCALAR", + "name": "Boolean", + "ofType": null + } + }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "onFragment", + "description": null, + "args": [], + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "SCALAR", + "name": "Boolean", + "ofType": null + } + }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "onOperation", + "description": null, + "args": [], + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "SCALAR", + "name": "Boolean", + "ofType": null + } + }, + "isDeprecated": false, + "deprecationReason": null + } + ], + "inputFields": null, + "interfaces": [], + "enumValues": null, + "possibleTypes": null + }, + { + "kind": "OBJECT", + "name": "__InputValue", + "description": "Arguments provided to Fields or Directives and the input fields of an InputObject are represented as Input Values which describe their type and optionally a default value.", + "fields": [ + { + "name": "defaultValue", + "description": null, + "args": [], + "type": { + "kind": "SCALAR", + "name": "String", + "ofType": null + }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "description", + "description": null, + "args": [], + "type": { + "kind": "SCALAR", + "name": "String", + "ofType": null + }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "name", + "description": null, + "args": [], + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "SCALAR", + "name": "String", + "ofType": null + } + }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "type", + "description": null, + "args": [], + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "OBJECT", + "name": "__Type", + "ofType": null + } + }, + "isDeprecated": false, + "deprecationReason": null + } + ], + "inputFields": null, + "interfaces": [], + "enumValues": null, + "possibleTypes": null + }, + { + "kind": "OBJECT", + "name": "__Type", + "description": "The fundamental unit of any GraphQL Schema is the type. There are many kinds of types in GraphQL as represented by the \u0060__TypeKind\u0060 enum. Depending on the kind of a type, certain fields describe information about that type. Scalar types provide no information beyond a name and description, while Enum types provide their values. Object and Interface types provide the fields they describe. Abstract types, Union and Interface, provide the Object types possible at runtime. List and NonNull types compose other types.", + "fields": [ + { + "name": "description", + "description": null, + "args": [], + "type": { + "kind": "SCALAR", + "name": "String", + "ofType": null + }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "enumValues", + "description": null, + "args": [ + { + "name": "includeDeprecated", + "description": null, + "type": { + "kind": "SCALAR", + "name": "Boolean", + "ofType": null + }, + "defaultValue": "false" + } + ], + "type": { + "kind": "LIST", + "name": null, + "ofType": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "OBJECT", + "name": "__EnumValue", + "ofType": null + } + } + }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "fields", + "description": null, + "args": [ + { + "name": "includeDeprecated", + "description": null, + "type": { + "kind": "SCALAR", + "name": "Boolean", + "ofType": null + }, + "defaultValue": "false" + } + ], + "type": { + "kind": "LIST", + "name": null, + "ofType": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "OBJECT", + "name": "__Field", + "ofType": null + } + } + }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "inputFields", + "description": null, + "args": [], + "type": { + "kind": "LIST", + "name": null, + "ofType": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "OBJECT", + "name": "__InputValue", + "ofType": null + } + } + }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "interfaces", + "description": null, + "args": [], + "type": { + "kind": "LIST", + "name": null, + "ofType": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "OBJECT", + "name": "__Type", + "ofType": null + } + } + }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "kind", + "description": null, + "args": [], + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "ENUM", + "name": "__TypeKind", + "ofType": null + } + }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "name", + "description": null, + "args": [], + "type": { + "kind": "SCALAR", + "name": "String", + "ofType": null + }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "ofType", + "description": null, + "args": [], + "type": { + "kind": "OBJECT", + "name": "__Type", + "ofType": null + }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "possibleTypes", + "description": null, + "args": [], + "type": { + "kind": "LIST", + "name": null, + "ofType": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "OBJECT", + "name": "__Type", + "ofType": null + } + } + }, + "isDeprecated": false, + "deprecationReason": null + } + ], + "inputFields": null, + "interfaces": [], + "enumValues": null, + "possibleTypes": null + }, + { + "kind": "OBJECT", + "name": "__EnumValue", + "description": "One possible value for a given Enum. Enum values are unique values, not a placeholder for a string or numeric value. However an Enum value is returned in a JSON response as a string.", + "fields": [ + { + "name": "deprecationReason", + "description": null, + "args": [], + "type": { + "kind": "SCALAR", + "name": "String", + "ofType": null + }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "description", + "description": null, + "args": [], + "type": { + "kind": "SCALAR", + "name": "String", + "ofType": null + }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "isDeprecated", + "description": null, + "args": [], + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "SCALAR", + "name": "Boolean", + "ofType": null + } + }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "name", + "description": null, + "args": [], + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "SCALAR", + "name": "String", + "ofType": null + } + }, + "isDeprecated": false, + "deprecationReason": null + } + ], + "inputFields": null, + "interfaces": [], + "enumValues": null, + "possibleTypes": null + }, + { + "kind": "OBJECT", + "name": "__Field", + "description": "Object and Interface types are described by a list of Fields, each of which has a name, potentially a list of arguments, and a return type.", + "fields": [ + { + "name": "args", + "description": null, + "args": [], + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "LIST", + "name": null, + "ofType": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "OBJECT", + "name": "__InputValue", + "ofType": null + } + } + } + }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "deprecationReason", + "description": null, + "args": [], + "type": { + "kind": "SCALAR", + "name": "String", + "ofType": null + }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "description", + "description": null, + "args": [], + "type": { + "kind": "SCALAR", + "name": "String", + "ofType": null + }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "isDeprecated", + "description": null, + "args": [], + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "SCALAR", + "name": "Boolean", + "ofType": null + } + }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "name", + "description": null, + "args": [], + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "SCALAR", + "name": "String", + "ofType": null + } + }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "type", + "description": null, + "args": [], + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "OBJECT", + "name": "__Type", + "ofType": null + } + }, + "isDeprecated": false, + "deprecationReason": null + } + ], + "inputFields": null, + "interfaces": [], + "enumValues": null, + "possibleTypes": null + }, + { + "kind": "ENUM", + "name": "__TypeKind", + "description": "An enum describing what kind of type a given __Type is.", + "fields": null, + "inputFields": null, + "interfaces": null, + "enumValues": [ + { + "name": "SCALAR", + "description": "Indicates this type is a scalar.", + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "OBJECT", + "description": "Indicates this type is an object. \u0060fields\u0060 and \u0060interfaces\u0060 are valid fields.", + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "INTERFACE", + "description": "Indicates this type is an interface. \u0060fields\u0060 and \u0060possibleTypes\u0060 are valid fields.", + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "UNION", + "description": "Indicates this type is a union. \u0060possibleTypes\u0060 is a valid field.", + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "ENUM", + "description": "Indicates this type is an enum. \u0060enumValues\u0060 is a valid field.", + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "INPUT_OBJECT", + "description": "Indicates this type is an input object. \u0060inputFields\u0060 is a valid field.", + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "LIST", + "description": "Indicates this type is a list. \u0060ofType\u0060 is a valid field.", + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "NON_NULL", + "description": "Indicates this type is a non-null. \u0060ofType\u0060 is a valid field.", + "isDeprecated": false, + "deprecationReason": null + } + ], + "possibleTypes": null + }, + { + "kind": "ENUM", + "name": "__DirectiveLocation", + "description": "A Directive can be adjacent to many parts of the GraphQL language, a __DirectiveLocation describes one such possible adjacencies.", + "fields": null, + "inputFields": null, + "interfaces": null, + "enumValues": [ + { + "name": "QUERY", + "description": "Location adjacent to a query operation.", + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "MUTATION", + "description": "Location adjacent to a mutation operation.", + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "SUBSCRIPTION", + "description": "Location adjacent to a subscription operation.", + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "FIELD", + "description": "Location adjacent to a field.", + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "FRAGMENT_DEFINITION", + "description": "Location adjacent to a fragment definition.", + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "FRAGMENT_SPREAD", + "description": "Location adjacent to a fragment spread.", + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "INLINE_FRAGMENT", + "description": "Location adjacent to an inline fragment.", + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "SCHEMA", + "description": "Location adjacent to a schema IDL definition.", + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "SCALAR", + "description": "Location adjacent to a scalar IDL definition.", + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "OBJECT", + "description": "Location adjacent to an object IDL definition.", + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "FIELD_DEFINITION", + "description": "Location adjacent to a field IDL definition.", + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "ARGUMENT_DEFINITION", + "description": "Location adjacent to a field argument IDL definition.", + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "INTERFACE", + "description": "Location adjacent to an interface IDL definition.", + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "UNION", + "description": "Location adjacent to an union IDL definition.", + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "ENUM", + "description": "Location adjacent to an enum IDL definition.", + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "ENUM_VALUE", + "description": "Location adjacent to an enum value definition.", + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "INPUT_OBJECT", + "description": "Location adjacent to an input object IDL definition.", + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "INPUT_FIELD_DEFINITION", + "description": "Location adjacent to an input object field IDL definition.", + "isDeprecated": false, + "deprecationReason": null + } + ], + "possibleTypes": null + }, + { + "kind": "OBJECT", + "name": "Query", + "description": "The query type.", + "fields": [ + { + "name": "alwaysError", + "description": "Always produces an execution error for integration tests.", + "args": [], + "type": { + "kind": "SCALAR", + "name": "String", + "ofType": null + }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "echo", + "description": "Enters an input type and get it back.", + "args": [ + { + "name": "input", + "description": "The input to be echoed as an output.", + "type": { + "kind": "INPUT_OBJECT", + "name": "Input", + "ofType": null + }, + "defaultValue": null + } + ], + "type": { + "kind": "OBJECT", + "name": "Output", + "ofType": null + }, + "isDeprecated": false, + "deprecationReason": null + } + ], + "inputFields": null, + "interfaces": [], + "enumValues": null, + "possibleTypes": null + }, + { + "kind": "OBJECT", + "name": "Output", + "description": "The output for an input.", + "fields": [ + { + "name": "list", + "description": "A list of output fields.", + "args": [], + "type": { + "kind": "LIST", + "name": null, + "ofType": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "OBJECT", + "name": "OutputField", + "ofType": null + } + } + }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "single", + "description": "A single output field.", + "args": [], + "type": { + "kind": "OBJECT", + "name": "OutputField", + "ofType": null + }, + "isDeprecated": false, + "deprecationReason": null + } + ], + "inputFields": null, + "interfaces": [], + "enumValues": null, + "possibleTypes": null + }, + { + "kind": "OBJECT", + "name": "OutputField", + "description": "The output for a field input.", + "fields": [ + { + "name": "deprecated", + "description": "A string value through a deprecated field.", + "args": [], + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "SCALAR", + "name": "String", + "ofType": null + } + }, + "isDeprecated": true, + "deprecationReason": "This field is deprecated." + }, + { + "name": "guid", + "description": "A Guid value.", + "args": [], + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "SCALAR", + "name": "Guid", + "ofType": null + } + }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "guidId", + "description": "A Guid Id value.", + "args": [], + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "SCALAR", + "name": "ID", + "ofType": null + } + }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "guidIdOption", + "description": "A Guid Id value.", + "args": [], + "type": { + "kind": "SCALAR", + "name": "ID", + "ofType": null + }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "int", + "description": "An integer value.", + "args": [], + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "SCALAR", + "name": "Int", + "ofType": null + } + }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "intOption", + "description": "An integer option value.", + "args": [], + "type": { + "kind": "SCALAR", + "name": "Int", + "ofType": null + }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "string", + "description": "A string value.", + "args": [], + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "SCALAR", + "name": "String", + "ofType": null + } + }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "stringId", + "description": "A String Id value.", + "args": [], + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "SCALAR", + "name": "ID", + "ofType": null + } + }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "stringIdOption", + "description": "A String Id value.", + "args": [], + "type": { + "kind": "SCALAR", + "name": "ID", + "ofType": null + }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "stringOption", + "description": "A string option value.", + "args": [], + "type": { + "kind": "SCALAR", + "name": "String", + "ofType": null + }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "uri", + "description": "An URI value.", + "args": [], + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "SCALAR", + "name": "URI", + "ofType": null + } + }, + "isDeprecated": false, + "deprecationReason": null + } + ], + "inputFields": null, + "interfaces": [], + "enumValues": null, + "possibleTypes": null + }, + { + "kind": "SCALAR", + "name": "Guid", + "description": "The \u0060Guid\u0060 scalar type represents a Globally Unique Identifier value. It\u0027s a 128-bit long byte key, that can be serialized to string.", + "fields": null, + "inputFields": null, + "interfaces": null, + "enumValues": null, + "possibleTypes": null + }, + { + "kind": "INPUT_OBJECT", + "name": "Input", + "description": "Input object type.", + "fields": null, + "inputFields": [ + { + "name": "single", + "description": "A single input field.", + "type": { + "kind": "INPUT_OBJECT", + "name": "InputField", + "ofType": null + }, + "defaultValue": null + }, + { + "name": "list", + "description": "A list of input fields.", + "type": { + "kind": "LIST", + "name": null, + "ofType": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "INPUT_OBJECT", + "name": "InputField", + "ofType": null + } + } + }, + "defaultValue": null + } + ], + "interfaces": null, + "enumValues": null, + "possibleTypes": null + }, + { + "kind": "INPUT_OBJECT", + "name": "InputField", + "description": null, + "fields": null, + "inputFields": [ + { + "name": "string", + "description": "A string value.", + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "SCALAR", + "name": "String", + "ofType": null + } + }, + "defaultValue": null + }, + { + "name": "int", + "description": "An integer value.", + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "SCALAR", + "name": "Int", + "ofType": null + } + }, + "defaultValue": null + }, + { + "name": "stringOption", + "description": "A string option value.", + "type": { + "kind": "SCALAR", + "name": "String", + "ofType": null + }, + "defaultValue": null + }, + { + "name": "intOption", + "description": "An integer option value.", + "type": { + "kind": "SCALAR", + "name": "Int", + "ofType": null + }, + "defaultValue": null + }, + { + "name": "uri", + "description": "An URI value.", + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "SCALAR", + "name": "URI", + "ofType": null + } + }, + "defaultValue": null + }, + { + "name": "guid", + "description": "A Guid value.", + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "SCALAR", + "name": "Guid", + "ofType": null + } + }, + "defaultValue": null + } + ], + "interfaces": null, + "enumValues": null, + "possibleTypes": null + }, + { + "kind": "OBJECT", + "name": "Mutation", + "description": null, + "fields": [ + { + "name": "multipleUpload", + "description": "Uploads a list of files to the server and get them back.", + "args": [ + { + "name": "files", + "description": "The files to upload.", + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "LIST", + "name": null, + "ofType": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "INPUT_OBJECT", + "name": "File", + "ofType": null + } + } + } + }, + "defaultValue": null + } + ], + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "LIST", + "name": null, + "ofType": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "OBJECT", + "name": "UploadedFile", + "ofType": null + } + } + } + }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "nullableMultipleNullableUpload", + "description": "Uploads (maybe) a list of files (maybe) to the server and get them back (maybe).", + "args": [ + { + "name": "files", + "description": "The files to upload.", + "type": { + "kind": "LIST", + "name": null, + "ofType": { + "kind": "INPUT_OBJECT", + "name": "File", + "ofType": null + } + }, + "defaultValue": null + } + ], + "type": { + "kind": "LIST", + "name": null, + "ofType": { + "kind": "OBJECT", + "name": "UploadedFile", + "ofType": null + } + }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "nullableMultipleUpload", + "description": "Uploads (maybe) a list of files to the server and get them back (maybe).", + "args": [ + { + "name": "files", + "description": "The files to upload.", + "type": { + "kind": "LIST", + "name": null, + "ofType": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "INPUT_OBJECT", + "name": "File", + "ofType": null + } + } + }, + "defaultValue": null + } + ], + "type": { + "kind": "LIST", + "name": null, + "ofType": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "OBJECT", + "name": "UploadedFile", + "ofType": null + } + } + }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "nullableSingleUpload", + "description": "Uploads (maybe) a single file to the server and get it back (maybe).", + "args": [ + { + "name": "file", + "description": "The file to be uploaded.", + "type": { + "kind": "INPUT_OBJECT", + "name": "File", + "ofType": null + }, + "defaultValue": null + } + ], + "type": { + "kind": "OBJECT", + "name": "UploadedFile", + "ofType": null + }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "singleUpload", + "description": "Uploads a single file to the server and get it back.", + "args": [ + { + "name": "file", + "description": "The file to be uploaded.", + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "INPUT_OBJECT", + "name": "File", + "ofType": null + } + }, + "defaultValue": null + } + ], + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "OBJECT", + "name": "UploadedFile", + "ofType": null + } + }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "uploadComplex", + "description": "", + "args": [ + { + "name": "input", + "description": null, + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "INPUT_OBJECT", + "name": "InputFile", + "ofType": null + } + }, + "defaultValue": null + } + ], + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "SCALAR", + "name": "String", + "ofType": null + } + }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "uploadRequest", + "description": "Upload several files in different forms.", + "args": [ + { + "name": "request", + "description": "The request for uploading several files in different forms.", + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "INPUT_OBJECT", + "name": "UploadRequest", + "ofType": null + } + }, + "defaultValue": null + } + ], + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "OBJECT", + "name": "UploadResponse", + "ofType": null + } + }, + "isDeprecated": false, + "deprecationReason": null + } + ], + "inputFields": null, + "interfaces": [], + "enumValues": null, + "possibleTypes": null + }, + { + "kind": "OBJECT", + "name": "UploadedFile", + "description": "Contains data of an uploaded file.", + "fields": [ + { + "name": "contentAsText", + "description": "The content of the file as text.", + "args": [], + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "SCALAR", + "name": "String", + "ofType": null + } + }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "contentType", + "description": "The content type of the file.", + "args": [], + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "SCALAR", + "name": "String", + "ofType": null + } + }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "name", + "description": "The name of the file.", + "args": [], + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "SCALAR", + "name": "String", + "ofType": null + } + }, + "isDeprecated": false, + "deprecationReason": null + } + ], + "inputFields": null, + "interfaces": [], + "enumValues": null, + "possibleTypes": null + }, + { + "kind": "INPUT_OBJECT", + "name": "File", + "description": "The \u0060File\u0060 type represents a file on one or more fields of an object in an object list. The filter is represented by a JSON object where the fields are the complemented by specific suffixes to represent a query.", + "fields": null, + "inputFields": [], + "interfaces": null, + "enumValues": null, + "possibleTypes": null + }, + { + "kind": "INPUT_OBJECT", + "name": "InputFile", + "description": null, + "fields": null, + "inputFields": [ + { + "name": "file", + "description": null, + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "INPUT_OBJECT", + "name": "File", + "ofType": null + } + }, + "defaultValue": null + } + ], + "interfaces": null, + "enumValues": null, + "possibleTypes": null + }, + { + "kind": "OBJECT", + "name": "UploadResponse", + "description": "Contains uploaded files of an upload files request.", + "fields": [ + { + "name": "multiple", + "description": "Multiple file uploads.", + "args": [], + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "LIST", + "name": null, + "ofType": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "OBJECT", + "name": "UploadedFile", + "ofType": null + } + } + } + }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "nullableMultiple", + "description": "Optional list of multiple file uploads.", + "args": [], + "type": { + "kind": "LIST", + "name": null, + "ofType": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "OBJECT", + "name": "UploadedFile", + "ofType": null + } + } + }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "nullableMultipleNullable", + "description": "Optional list of multiple optional file uploads.", + "args": [], + "type": { + "kind": "LIST", + "name": null, + "ofType": { + "kind": "OBJECT", + "name": "UploadedFile", + "ofType": null + } + }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "single", + "description": "A single file upload.", + "args": [], + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "OBJECT", + "name": "UploadedFile", + "ofType": null + } + }, + "isDeprecated": false, + "deprecationReason": null + } + ], + "inputFields": null, + "interfaces": [], + "enumValues": null, + "possibleTypes": null + }, + { + "kind": "INPUT_OBJECT", + "name": "UploadRequest", + "description": "Request for uploading files in several different forms.", + "fields": null, + "inputFields": [ + { + "name": "single", + "description": "A single file upload.", + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "INPUT_OBJECT", + "name": "File", + "ofType": null + } + }, + "defaultValue": null + }, + { + "name": "multiple", + "description": "Multiple file uploads.", + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "LIST", + "name": null, + "ofType": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "INPUT_OBJECT", + "name": "File", + "ofType": null + } + } + } + }, + "defaultValue": null + }, + { + "name": "nullableMultiple", + "description": "Optional list of multiple file uploads.", + "type": { + "kind": "LIST", + "name": null, + "ofType": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "INPUT_OBJECT", + "name": "File", + "ofType": null + } + } + }, + "defaultValue": null + }, + { + "name": "nullableMultipleNullable", + "description": "Optional list of multiple optional file uploads.", + "type": { + "kind": "LIST", + "name": null, + "ofType": { + "kind": "INPUT_OBJECT", + "name": "File", + "ofType": null + } + }, + "defaultValue": null + } + ], + "interfaces": null, + "enumValues": null, + "possibleTypes": null + } + ], + "directives": [ + { + "name": "include", + "description": "Directs the executor to include this field or fragment only when the \u0060if\u0060 argument is true.", + "locations": [ + "FIELD", + "FRAGMENT_SPREAD", + "INLINE_FRAGMENT" + ], + "args": [ + { + "name": "if", + "description": "Included when true.", + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "SCALAR", + "name": "Boolean", + "ofType": null + } + }, + "defaultValue": null + } + ] + }, + { + "name": "skip", + "description": "Directs the executor to skip this field or fragment when the \u0060if\u0060 argument is true.", + "locations": [ + "FIELD", + "FRAGMENT_SPREAD", + "INLINE_FRAGMENT" + ], + "args": [ + { + "name": "if", + "description": "Skipped when true.", + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "SCALAR", + "name": "Boolean", + "ofType": null + } + }, + "defaultValue": null + } + ] + }, + { + "name": "defer", + "description": "Defers the resolution of this field or fragment", + "locations": [ + "FIELD", + "FRAGMENT_DEFINITION", + "FRAGMENT_SPREAD", + "INLINE_FRAGMENT" + ], + "args": [] + }, + { + "name": "stream", + "description": "Streams the resolution of this field or fragment", + "locations": [ + "FIELD", + "FRAGMENT_DEFINITION", + "FRAGMENT_SPREAD", + "INLINE_FRAGMENT" + ], + "args": [] + }, + { + "name": "live", + "description": "Subscribes for live updates of this field or fragment", + "locations": [ + "FIELD", + "FRAGMENT_DEFINITION", + "FRAGMENT_SPREAD", + "INLINE_FRAGMENT" + ], + "args": [] + } + ] + } + } +} \ No newline at end of file diff --git a/tests/FSharp.Data.GraphQL.IntegrationTests/introspection.json b/tests/FSharp.Data.GraphQL.IntegrationTests/introspection.json index 7b3d9abcf..a961111fd 100644 --- a/tests/FSharp.Data.GraphQL.IntegrationTests/introspection.json +++ b/tests/FSharp.Data.GraphQL.IntegrationTests/introspection.json @@ -1,5 +1,5 @@ { - "documentId": -128167532, + "documentId": 195530235, "data": { "__schema": { "queryType": { From 1506a22528d916b044f3227faa7f1f906430a607 Mon Sep 17 00:00:00 2001 From: Copilot <198982749+Copilot@users.noreply.github.com> Date: Sun, 17 May 2026 20:02:19 +0200 Subject: [PATCH 02/11] Enforce input/output kind safety for `ListOf`/`Nullable` wrappers at compile time (#569) Co-authored-by: xperiandri <2365592+xperiandri@users.noreply.github.com> Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com> Co-authored-by: Andrii Chebukin --- .../SchemaDefinitions.fs | 86 +++++++++-- .../SchemaDefinitionsExtensions.fs | 6 +- src/FSharp.Data.GraphQL.Shared/TypeSystem.fs | 39 +++-- .../FSharp.Data.GraphQL.Tests.fsproj | 10 ++ .../PlanningTests.fs | 8 +- .../TypeWrappersKindSafety/.gitignore | 1 + .../ListOf.InputAsOutput.fsx | 12 ++ .../ListOf.OutputAsInput.fsx | 12 ++ .../Nullable.InputAsOutput.fsx | 12 ++ .../Nullable.OutputAsInput.fsx | 12 ++ .../StructNullable.InputAsOutput.fsx | 12 ++ .../StructNullable.OutputAsInput.fsx | 12 ++ .../TypeWrappersKindSafety/Valid.fsx | 20 +++ .../TypeWrappersKindSafetyTests.fs | 143 ++++++++++++++++++ .../Variables and Inputs/InputComplexTests.fs | 2 +- .../Variables and Inputs/InputNestedTests.fs | 2 +- 16 files changed, 354 insertions(+), 35 deletions(-) create mode 100644 tests/FSharp.Data.GraphQL.Tests/TypeWrappersKindSafety/.gitignore create mode 100644 tests/FSharp.Data.GraphQL.Tests/TypeWrappersKindSafety/ListOf.InputAsOutput.fsx create mode 100644 tests/FSharp.Data.GraphQL.Tests/TypeWrappersKindSafety/ListOf.OutputAsInput.fsx create mode 100644 tests/FSharp.Data.GraphQL.Tests/TypeWrappersKindSafety/Nullable.InputAsOutput.fsx create mode 100644 tests/FSharp.Data.GraphQL.Tests/TypeWrappersKindSafety/Nullable.OutputAsInput.fsx create mode 100644 tests/FSharp.Data.GraphQL.Tests/TypeWrappersKindSafety/StructNullable.InputAsOutput.fsx create mode 100644 tests/FSharp.Data.GraphQL.Tests/TypeWrappersKindSafety/StructNullable.OutputAsInput.fsx create mode 100644 tests/FSharp.Data.GraphQL.Tests/TypeWrappersKindSafety/Valid.fsx create mode 100644 tests/FSharp.Data.GraphQL.Tests/TypeWrappersKindSafetyTests.fs diff --git a/src/FSharp.Data.GraphQL.Shared/SchemaDefinitions.fs b/src/FSharp.Data.GraphQL.Shared/SchemaDefinitions.fs index 51f8682c5..bc33ab3e8 100644 --- a/src/FSharp.Data.GraphQL.Shared/SchemaDefinitions.fs +++ b/src/FSharp.Data.GraphQL.Shared/SchemaDefinitions.fs @@ -392,17 +392,73 @@ module SchemaDefinitions = | false, _ -> getParseError destinationType s | InlineConstant value -> value.GetCoerceError destinationType - /// Wraps a GraphQL type definition, allowing defining field/argument - /// to take option of provided value. - let Nullable(innerDef : #TypeDef<'Val>) : NullableDef<'Val> = upcast { NullableDefinition.OfType = innerDef } - - /// Wraps a GraphQL type definition, allowing defining field/argument - /// to take voption of provided value. - let StructNullable(innerDef : #TypeDef<'Val>) : StructNullableDef<'Val> = upcast { StructNullableDefinition.OfType = innerDef } - - /// Wraps a GraphQL type definition, allowing defining field/argument - /// to take collection of provided value. - let ListOf(innerDef : #TypeDef<'Val>) : ListOfDef<'Val, 'Seq> = upcast { ListOfDefinition.OfType = innerDef } + type TypeWrapperStaticDispatch = + + static member Nullable<'Val>(innerDef : InputOutputDef<'Val>) : NullableDef<'Val> = + let ofType : TypeDef<'Val> = upcast innerDef + upcast { NullableDefinition.OfType = ofType } + + static member Nullable<'Val>(innerDef : InputDef<'Val>) : InputDef<'Val option> = + let ofType : TypeDef<'Val> = upcast innerDef + upcast { NullableDefinition.OfType = ofType } + + static member Nullable<'Val>(innerDef : OutputDef<'Val>) : OutputDef<'Val option> = + let ofType : TypeDef<'Val> = upcast innerDef + upcast { NullableDefinition.OfType = ofType } + + static member StructNullable<'Val>(innerDef : InputOutputDef<'Val>) : StructNullableDef<'Val> = + let ofType : TypeDef<'Val> = upcast innerDef + upcast { StructNullableDefinition.OfType = ofType } + + static member StructNullable<'Val>(innerDef : InputDef<'Val>) : InputDef<'Val voption> = + let ofType : TypeDef<'Val> = upcast innerDef + upcast { StructNullableDefinition.OfType = ofType } + + static member StructNullable<'Val>(innerDef : OutputDef<'Val>) : OutputDef<'Val voption> = + let ofType : TypeDef<'Val> = upcast innerDef + upcast { StructNullableDefinition.OfType = ofType } + + static member ListOf<'Val, 'Seq when 'Seq :> 'Val seq>(innerDef : InputOutputDef<'Val>) : ListOfDef<'Val, 'Seq> = + let ofType : TypeDef<'Val> = upcast innerDef + upcast { ListOfDefinition.OfType = ofType } + + static member ListOf<'Val, 'Seq when 'Seq :> 'Val seq>(innerDef : InputDef<'Val>) : InputDef<'Seq> = + let ofType : TypeDef<'Val> = upcast innerDef + upcast { ListOfDefinition.OfType = ofType } + + static member ListOf<'Val, 'Seq when 'Seq :> 'Val seq>(innerDef : OutputDef<'Val>) : OutputDef<'Seq> = + let ofType : TypeDef<'Val> = upcast innerDef + upcast { ListOfDefinition.OfType = ofType } + + /// Wraps a GraphQL input or output type definition, allowing defining field/argument + /// to take option of provided value while preserving input/output kind of wrapped type. + /// Input wrappers produce input definitions, output wrappers produce output definitions, + /// and wrappers over types implementing both kinds keep both capabilities. + /// Dispatch is selected at compile time via SRTP. + let inline Nullable< ^Def, ^Wrapped when (^Def or TypeWrapperStaticDispatch) : (static member Nullable : ^Def -> ^Wrapped) > + (innerDef : ^Def) + : ^Wrapped = + ((^Def or TypeWrapperStaticDispatch) : (static member Nullable : ^Def -> ^Wrapped) innerDef) + + /// Wraps a GraphQL input or output type definition, allowing defining field/argument + /// to take voption of provided value while preserving input/output kind of wrapped type. + /// Input wrappers produce input definitions, output wrappers produce output definitions, + /// and wrappers over types implementing both kinds keep both capabilities. + /// Dispatch is selected at compile time via SRTP. + let inline StructNullable< ^Def, ^Wrapped when (^Def or TypeWrapperStaticDispatch) : (static member StructNullable : ^Def -> ^Wrapped) > + (innerDef : ^Def) + : ^Wrapped = + ((^Def or TypeWrapperStaticDispatch) : (static member StructNullable : ^Def -> ^Wrapped) innerDef) + + /// Wraps a GraphQL input or output type definition, allowing defining field/argument + /// to take collection of provided value while preserving input/output kind of wrapped type. + /// Input wrappers produce input definitions, output wrappers produce output definitions, + /// and wrappers over types implementing both kinds keep both capabilities. + /// Dispatch is selected at compile time via SRTP. + let inline ListOf< ^Def, ^Wrapped when (^Def or TypeWrapperStaticDispatch) : (static member ListOf : ^Def -> ^Wrapped) > + (innerDef : ^Def) + : ^Wrapped = + ((^Def or TypeWrapperStaticDispatch) : (static member ListOf : ^Def -> ^Wrapped) innerDef) let internal variableOrElse other (_ : InputExecutionContextProvider) value (variables : IReadOnlyDictionary) = match value with @@ -1415,13 +1471,14 @@ module SchemaDefinitions = /// If defined, this value will be used when no matching input has been provided by the requester. /// Optional input description. Usefull for generating documentation. static member SkippableInput(name : string, typedef : #InputDef<'In>, ?description : string) : InputFieldDef = + let typedef : InputDef<'In> = upcast typedef upcast { InputFieldDefinition.Name = name Description = description |> Option.map (fun s -> s + " Skip this field if you want to avoid saving it") IsSkippable = true TypeDef = - match (box typedef) with - | :? NullableDef<'In> as n -> n - | _ -> Nullable typedef + match (box typedef) with + | :? NullableDef<'In> as n -> (n :> InputDef<'In option>) + | _ -> Nullable typedef DefaultValue = None ExecuteInput = Unchecked.defaultof } @@ -1539,4 +1596,3 @@ module SchemaDefinitions = Description = description FieldsFn = fun () -> fieldsFn() |> List.toArray ResolveType = resolveType } - diff --git a/src/FSharp.Data.GraphQL.Shared/SchemaDefinitionsExtensions.fs b/src/FSharp.Data.GraphQL.Shared/SchemaDefinitionsExtensions.fs index b11909644..1da3341c7 100644 --- a/src/FSharp.Data.GraphQL.Shared/SchemaDefinitionsExtensions.fs +++ b/src/FSharp.Data.GraphQL.Shared/SchemaDefinitionsExtensions.fs @@ -27,8 +27,10 @@ type internal CustomFieldsObjectDefinition<'Val> (source : ObjectDef<'Val>, fiel member _.Implements = source.Implements member _.IsTypeOf = source.IsTypeOf interface TypeDef with - member this.MakeList () = upcast (ListOf this) - member this.MakeNullable () = upcast (Nullable this) + // We construct wrappers directly here because this API works with untyped TypeDef values. + // The public ListOf/Nullable helpers use SRTP dispatch and require statically known direction. + member this.MakeList () = upcast { ListOfDefinition.OfType = this } + member this.MakeNullable () = upcast { NullableDefinition.OfType = this } member _.Type = (source :> TypeDef).Type interface NamedDef with member _.Name = (source :> NamedDef).Name diff --git a/src/FSharp.Data.GraphQL.Shared/TypeSystem.fs b/src/FSharp.Data.GraphQL.Shared/TypeSystem.fs index 11006387b..be8e79b55 100644 --- a/src/FSharp.Data.GraphQL.Shared/TypeSystem.fs +++ b/src/FSharp.Data.GraphQL.Shared/TypeSystem.fs @@ -609,6 +609,24 @@ and OutputDef<'Val> = inherit TypeDef<'Val> end +/// Representation of type definitions that can be used as both inputs and outputs +/// (for example scalars and enums). This marker is also used by SRTP wrapper dispatch. +and InputOutputDef = + interface + inherit InputDef + inherit OutputDef + end + +/// Representation of all type definitions, that can be used as both inputs and outputs +/// and are constrained to represent the provided .NET type. +and InputOutputDef<'Val> = + interface + inherit InputOutputDef + inherit TypeDef<'Val> + inherit InputDef<'Val> + inherit OutputDef<'Val> + end + /// Representation of leaf type definitions. Leaf types represents leafs /// of the GraphQL query tree. Each query path must end with a leaf. /// By default only scalars and enums are valid leaf types. @@ -1067,8 +1085,7 @@ and ScalarDef = abstract CoerceOutput : obj -> obj option inherit TypeDef inherit NamedDef - inherit InputDef - inherit OutputDef + inherit InputOutputDef inherit LeafDef end @@ -1098,6 +1115,7 @@ and [] ScalarDefinition<'Primitive, 'Val> = { interface InputDef interface OutputDef + interface InputOutputDef interface ScalarDef with member x.Name = x.Name @@ -1107,6 +1125,7 @@ and [] ScalarDefinition<'Primitive, 'Val> = { interface InputDef<'Val> interface OutputDef<'Val> + interface InputOutputDef<'Val> interface LeafDef interface NamedDef with @@ -1182,8 +1201,7 @@ and EnumDef = /// List of available enum cases. abstract Options : EnumVal[] inherit TypeDef - inherit InputDef - inherit OutputDef + inherit InputOutputDef inherit LeafDef inherit NamedDef end @@ -1197,8 +1215,7 @@ and EnumDef<'Val> = abstract Options : EnumValue<'Val>[] inherit EnumDef inherit TypeDef<'Val> - inherit InputDef<'Val> - inherit OutputDef<'Val> + inherit InputOutputDef<'Val> end and internal EnumDefinition<'Val> = { @@ -1212,6 +1229,7 @@ and internal EnumDefinition<'Val> = { interface InputDef interface OutputDef + interface InputOutputDef interface TypeDef with member _.Type = typeof<'Val> @@ -1523,8 +1541,7 @@ and ListOfDef<'Val, 'Seq when 'Seq :> 'Val seq> = /// GraphQL type definition of the container element type. abstract OfType : TypeDef<'Val> inherit TypeDef<'Seq> - inherit InputDef<'Seq> - inherit OutputDef<'Seq> + inherit InputOutputDef<'Seq> inherit ListOfDef end @@ -1574,8 +1591,7 @@ and NullableDef<'Val> = interface /// GraphQL type definition of the nested type. abstract OfType : TypeDef<'Val> - inherit InputDef<'Val option> - inherit OutputDef<'Val option> + inherit InputOutputDef<'Val option> inherit NullableDef end @@ -1614,8 +1630,7 @@ and StructNullableDef<'Val> = interface /// GraphQL type definition of the nested type. abstract OfType : TypeDef<'Val> - inherit InputDef<'Val voption> - inherit OutputDef<'Val voption> + inherit InputOutputDef<'Val voption> inherit NullableDef end diff --git a/tests/FSharp.Data.GraphQL.Tests/FSharp.Data.GraphQL.Tests.fsproj b/tests/FSharp.Data.GraphQL.Tests/FSharp.Data.GraphQL.Tests.fsproj index 763d841ec..1bc224559 100644 --- a/tests/FSharp.Data.GraphQL.Tests/FSharp.Data.GraphQL.Tests.fsproj +++ b/tests/FSharp.Data.GraphQL.Tests/FSharp.Data.GraphQL.Tests.fsproj @@ -41,6 +41,16 @@ + + + + + + + + + + diff --git a/tests/FSharp.Data.GraphQL.Tests/PlanningTests.fs b/tests/FSharp.Data.GraphQL.Tests/PlanningTests.fs index 57b330da4..ad7cf74d5 100644 --- a/tests/FSharp.Data.GraphQL.Tests/PlanningTests.fs +++ b/tests/FSharp.Data.GraphQL.Tests/PlanningTests.fs @@ -139,7 +139,7 @@ let ``Planning must retain correct types for lists``() = } } }""" - let PersonList : ListOfDef = ListOf Person + let PersonList : OutputDef = ListOf Person let plan = schemaProcessor.CreateExecutionPlanOrFail(query) equals 1 plan.Fields.Length let listInfo = plan.Fields.Head @@ -178,7 +178,7 @@ let ``Planning must work with interfaces``() = }""" let plan = schemaProcessor.CreateExecutionPlanOrFail(query) equals 1 plan.Fields.Length - let INamedList : ListOfDef = ListOf INamed + let INamedList : OutputDef = ListOf INamed let listInfo = plan.Fields.Head listInfo.Identifier |> equals "names" listInfo.ReturnDef |> equals (upcast INamedList) @@ -215,7 +215,7 @@ let ``Planning must work with unions``() = let plan = schemaProcessor.CreateExecutionPlanOrFail(query) equals 1 plan.Fields.Length let listInfo = plan.Fields.Head - let UNamedList : ListOfDef = ListOf UNamed + let UNamedList : OutputDef = ListOf UNamed listInfo.Identifier |> equals "names" listInfo.ReturnDef |> equals (upcast UNamedList) let (ResolveCollection(info)) = listInfo.Kind @@ -309,7 +309,7 @@ let ``Planning must handle inline fragment with non-matching type condition in u // Verify the execution plan structure equals 1 plan.Fields.Length let listInfo = plan.Fields.Head - let UNamedList : ListOfDef = ListOf UNamed + let UNamedList : OutputDef = ListOf UNamed listInfo.Identifier |> equals "names" listInfo.ReturnDef |> equals (upcast UNamedList) let (ResolveCollection(info)) = listInfo.Kind diff --git a/tests/FSharp.Data.GraphQL.Tests/TypeWrappersKindSafety/.gitignore b/tests/FSharp.Data.GraphQL.Tests/TypeWrappersKindSafety/.gitignore new file mode 100644 index 000000000..a6b4595a5 --- /dev/null +++ b/tests/FSharp.Data.GraphQL.Tests/TypeWrappersKindSafety/.gitignore @@ -0,0 +1 @@ +References.fsx diff --git a/tests/FSharp.Data.GraphQL.Tests/TypeWrappersKindSafety/ListOf.InputAsOutput.fsx b/tests/FSharp.Data.GraphQL.Tests/TypeWrappersKindSafety/ListOf.InputAsOutput.fsx new file mode 100644 index 000000000..d39472e9c --- /dev/null +++ b/tests/FSharp.Data.GraphQL.Tests/TypeWrappersKindSafety/ListOf.InputAsOutput.fsx @@ -0,0 +1,12 @@ +#load "References.fsx" + +open FSharp.Data.GraphQL.Types + +type InputOnly = { Value : int } +type OutputOnly = { Value : int } + +let inputOnlyType = + Define.InputObject (name = "InputOnlyType", fields = [ Define.Input ("value", IntType) ]) + +// This should fail: InputDef cannot be assigned to OutputDef +let _ : OutputDef = ListOf inputOnlyType diff --git a/tests/FSharp.Data.GraphQL.Tests/TypeWrappersKindSafety/ListOf.OutputAsInput.fsx b/tests/FSharp.Data.GraphQL.Tests/TypeWrappersKindSafety/ListOf.OutputAsInput.fsx new file mode 100644 index 000000000..dd3307625 --- /dev/null +++ b/tests/FSharp.Data.GraphQL.Tests/TypeWrappersKindSafety/ListOf.OutputAsInput.fsx @@ -0,0 +1,12 @@ +#load "References.fsx" + +open FSharp.Data.GraphQL.Types + +type InputOnly = { Value : int } +type OutputOnly = { Value : int } + +let outputOnlyType = + Define.Object (name = "OutputOnlyType", fields = [ Define.Field ("value", IntType, fun _ x -> x.Value) ]) + +// This should fail: OutputDef cannot be assigned to InputDef +let _ : InputDef = ListOf outputOnlyType diff --git a/tests/FSharp.Data.GraphQL.Tests/TypeWrappersKindSafety/Nullable.InputAsOutput.fsx b/tests/FSharp.Data.GraphQL.Tests/TypeWrappersKindSafety/Nullable.InputAsOutput.fsx new file mode 100644 index 000000000..cb37d0e38 --- /dev/null +++ b/tests/FSharp.Data.GraphQL.Tests/TypeWrappersKindSafety/Nullable.InputAsOutput.fsx @@ -0,0 +1,12 @@ +#load "References.fsx" + +open FSharp.Data.GraphQL.Types + +type InputOnly = { Value : int } +type OutputOnly = { Value : int } + +let inputOnlyType = + Define.InputObject (name = "InputOnlyType", fields = [ Define.Input ("value", IntType) ]) + +// This should fail: InputDef cannot be assigned to OutputDef +let _ : OutputDef = Nullable inputOnlyType diff --git a/tests/FSharp.Data.GraphQL.Tests/TypeWrappersKindSafety/Nullable.OutputAsInput.fsx b/tests/FSharp.Data.GraphQL.Tests/TypeWrappersKindSafety/Nullable.OutputAsInput.fsx new file mode 100644 index 000000000..faa94e83d --- /dev/null +++ b/tests/FSharp.Data.GraphQL.Tests/TypeWrappersKindSafety/Nullable.OutputAsInput.fsx @@ -0,0 +1,12 @@ +#load "References.fsx" + +open FSharp.Data.GraphQL.Types + +type InputOnly = { Value : int } +type OutputOnly = { Value : int } + +let outputOnlyType = + Define.Object (name = "OutputOnlyType", fields = [ Define.Field ("value", IntType, fun _ x -> x.Value) ]) + +// This should fail: OutputDef cannot be assigned to InputDef +let _ : InputDef = Nullable outputOnlyType diff --git a/tests/FSharp.Data.GraphQL.Tests/TypeWrappersKindSafety/StructNullable.InputAsOutput.fsx b/tests/FSharp.Data.GraphQL.Tests/TypeWrappersKindSafety/StructNullable.InputAsOutput.fsx new file mode 100644 index 000000000..1d1123457 --- /dev/null +++ b/tests/FSharp.Data.GraphQL.Tests/TypeWrappersKindSafety/StructNullable.InputAsOutput.fsx @@ -0,0 +1,12 @@ +#load "References.fsx" + +open FSharp.Data.GraphQL.Types + +type InputOnly = { Value : int } +type OutputOnly = { Value : int } + +let inputOnlyType = + Define.InputObject (name = "InputOnlyType", fields = [ Define.Input ("value", IntType) ]) + +// This should fail: InputDef cannot be assigned to OutputDef +let _ : OutputDef = StructNullable inputOnlyType diff --git a/tests/FSharp.Data.GraphQL.Tests/TypeWrappersKindSafety/StructNullable.OutputAsInput.fsx b/tests/FSharp.Data.GraphQL.Tests/TypeWrappersKindSafety/StructNullable.OutputAsInput.fsx new file mode 100644 index 000000000..e83c7e018 --- /dev/null +++ b/tests/FSharp.Data.GraphQL.Tests/TypeWrappersKindSafety/StructNullable.OutputAsInput.fsx @@ -0,0 +1,12 @@ +#load "References.fsx" + +open FSharp.Data.GraphQL.Types + +type InputOnly = { Value : int } +type OutputOnly = { Value : int } + +let outputOnlyType = + Define.Object (name = "OutputOnlyType", fields = [ Define.Field ("value", IntType, fun _ x -> x.Value) ]) + +// This should fail: OutputDef cannot be assigned to InputDef +let _ : InputDef = StructNullable outputOnlyType diff --git a/tests/FSharp.Data.GraphQL.Tests/TypeWrappersKindSafety/Valid.fsx b/tests/FSharp.Data.GraphQL.Tests/TypeWrappersKindSafety/Valid.fsx new file mode 100644 index 000000000..662d580f3 --- /dev/null +++ b/tests/FSharp.Data.GraphQL.Tests/TypeWrappersKindSafety/Valid.fsx @@ -0,0 +1,20 @@ +#load "References.fsx" + +open FSharp.Data.GraphQL.Types + +type InputOnly = { Value : int } +type OutputOnly = { Value : int } + +let inputOnlyType = + Define.InputObject (name = "InputOnlyType", fields = [ Define.Input ("value", IntType) ]) + +let outputOnlyType = + Define.Object (name = "OutputOnlyType", fields = [ Define.Field ("value", IntType, fun _ x -> x.Value) ]) + +// These are all valid assignments and must compile successfully +let _inputList : InputDef = ListOf inputOnlyType +let _outputList : OutputDef = ListOf outputOnlyType +let _inputNullable : InputDef = Nullable inputOnlyType +let _outputNullable : OutputDef = Nullable outputOnlyType +let _inputStruct : InputDef = StructNullable inputOnlyType +let _outputStruct : OutputDef = StructNullable outputOnlyType diff --git a/tests/FSharp.Data.GraphQL.Tests/TypeWrappersKindSafetyTests.fs b/tests/FSharp.Data.GraphQL.Tests/TypeWrappersKindSafetyTests.fs new file mode 100644 index 000000000..50bc6884e --- /dev/null +++ b/tests/FSharp.Data.GraphQL.Tests/TypeWrappersKindSafetyTests.fs @@ -0,0 +1,143 @@ +module FSharp.Data.GraphQL.Tests.TypeWrappersKindSafetyTests + +open System +open System.Diagnostics +open System.Threading.Tasks +open FSharp.Data.GraphQL.Types +open Xunit + +type private InputOnly = { Value : int } +type private OutputOnly = { Value : int } + +let private InputOnlyType = + Define.InputObject (name = "InputOnlyType", fields = [ Define.Input ("value", IntType) ]) + +let private OutputOnlyType = + Define.Object (name = "OutputOnlyType", fields = [ Define.Field ("value", IntType, fun _ x -> x.Value) ]) + +type TypeWrappersKindSafetyFixture () = + + let scriptsDir = IO.Path.Combine (AppContext.BaseDirectory, "TypeWrappersKindSafety") + let referencesPath = IO.Path.Combine (scriptsDir, "References.fsx") + let sourceProjectDir = + IO.Path.GetFullPath (IO.Path.Combine (AppContext.BaseDirectory, "..", "..", "..")) + let sourceScriptsDir = IO.Path.Combine (sourceProjectDir, "TypeWrappersKindSafety") + let sourceReferencesPath = IO.Path.Combine (sourceScriptsDir, "References.fsx") + + let ensureFileContentAsync (path : string) (content : string) : Task = task { + if IO.File.Exists (path) then + let! existing = IO.File.ReadAllTextAsync (path) + + if not (String.Equals (existing, content, StringComparison.Ordinal)) then + do! IO.File.WriteAllTextAsync (path, content) + else + do! IO.File.WriteAllTextAsync (path, content) + } + + member _.ScriptPath (name : string) = IO.Path.Combine (scriptsDir, name) + + member _.RunFsiCheckAsync (scriptPath : string) : Task = task { + let psi = + ProcessStartInfo ( + "dotnet", + sprintf "fsi --noninteractive \"%s\"" scriptPath, + UseShellExecute = false, + RedirectStandardOutput = true, + RedirectStandardError = true, + WorkingDirectory = AppContext.BaseDirectory + ) + + use proc = Process.Start (psi) + do! proc.WaitForExitAsync () + return proc.ExitCode + } + + member private _.ReferencesContent = + let sharedAssembly = IO.Path.Combine (AppContext.BaseDirectory, "FSharp.Data.GraphQL.Shared.dll") + let serverAssembly = IO.Path.Combine (AppContext.BaseDirectory, "FSharp.Data.GraphQL.Server.dll") + + [| sprintf "#r @\"%s\"" sharedAssembly; sprintf "#r @\"%s\"" serverAssembly |] + |> String.concat "\n" + + interface IAsyncLifetime with + + member this.InitializeAsync () : Task = task { + IO.Directory.CreateDirectory (scriptsDir) |> ignore + IO.Directory.CreateDirectory (sourceScriptsDir) |> ignore + + let content = this.ReferencesContent + + do! ensureFileContentAsync referencesPath content + do! ensureFileContentAsync sourceReferencesPath content + } + + member _.DisposeAsync () = Task.CompletedTask + +type TypeWrappersKindSafetyTests (fixture : TypeWrappersKindSafetyFixture) = + interface IClassFixture + + [] + member _.``ListOf keeps input-output direction`` () : Task = task { + let inputList : InputDef = ListOf InputOnlyType + let outputList : OutputDef = ListOf OutputOnlyType + Assert.Equal ("[InputOnlyType!]!", inputList.ToString ()) + Assert.Equal ("[OutputOnlyType!]!", outputList.ToString ()) + } + + [] + member _.``Nullable keeps input-output direction`` () : Task = task { + let nullableInput : InputDef = Nullable InputOnlyType + let nullableOutput : OutputDef = Nullable OutputOnlyType + Assert.Equal ("InputOnlyType", nullableInput.ToString ()) + Assert.Equal ("OutputOnlyType", nullableOutput.ToString ()) + } + + [] + member _.``StructNullable keeps input-output direction`` () : Task = task { + let nullableInput : InputDef = StructNullable InputOnlyType + let nullableOutput : OutputDef = StructNullable OutputOnlyType + Assert.Equal ("InputOnlyType", nullableInput.ToString ()) + Assert.Equal ("OutputOnlyType", nullableOutput.ToString ()) + } + + [] + member _.``Valid script compiles successfully`` () : Task = task { + let! exitCode = fixture.RunFsiCheckAsync (fixture.ScriptPath ("Valid.fsx")) + Assert.Equal (0, exitCode) + } + + [] + member _.``ListOf rejects output type as input at compile time`` () : Task = task { + let! exitCode = fixture.RunFsiCheckAsync (fixture.ScriptPath ("ListOf.OutputAsInput.fsx")) + Assert.NotEqual (0, exitCode) + } + + [] + member _.``ListOf rejects input type as output at compile time`` () : Task = task { + let! exitCode = fixture.RunFsiCheckAsync (fixture.ScriptPath ("ListOf.InputAsOutput.fsx")) + Assert.NotEqual (0, exitCode) + } + + [] + member _.``Nullable rejects output type as input at compile time`` () : Task = task { + let! exitCode = fixture.RunFsiCheckAsync (fixture.ScriptPath ("Nullable.OutputAsInput.fsx")) + Assert.NotEqual (0, exitCode) + } + + [] + member _.``Nullable rejects input type as output at compile time`` () : Task = task { + let! exitCode = fixture.RunFsiCheckAsync (fixture.ScriptPath ("Nullable.InputAsOutput.fsx")) + Assert.NotEqual (0, exitCode) + } + + [] + member _.``StructNullable rejects output type as input at compile time`` () : Task = task { + let! exitCode = fixture.RunFsiCheckAsync (fixture.ScriptPath ("StructNullable.OutputAsInput.fsx")) + Assert.NotEqual (0, exitCode) + } + + [] + member _.``StructNullable rejects input type as output at compile time`` () : Task = task { + let! exitCode = fixture.RunFsiCheckAsync (fixture.ScriptPath ("StructNullable.InputAsOutput.fsx")) + Assert.NotEqual (0, exitCode) + } diff --git a/tests/FSharp.Data.GraphQL.Tests/Variables and Inputs/InputComplexTests.fs b/tests/FSharp.Data.GraphQL.Tests/Variables and Inputs/InputComplexTests.fs index 954ace7ec..1e7053574 100644 --- a/tests/FSharp.Data.GraphQL.Tests/Variables and Inputs/InputComplexTests.fs +++ b/tests/FSharp.Data.GraphQL.Tests/Variables and Inputs/InputComplexTests.fs @@ -46,7 +46,7 @@ type TestInput = optArr : string option array option voptArr : string option array voption } // string voption array voption is too hard to implement -let InputArrayOf (innerDef : #TypeDef<'Val>) : ListOfDef<'Val, 'Val array> = ListOf innerDef +let InputArrayOf (innerDef : #InputDef<'Val>) : InputDef<'Val array> = ListOf (innerDef :> InputDef<'Val>) let TestInputObject = Define.InputObject ( diff --git a/tests/FSharp.Data.GraphQL.Tests/Variables and Inputs/InputNestedTests.fs b/tests/FSharp.Data.GraphQL.Tests/Variables and Inputs/InputNestedTests.fs index 69fc2b825..de5730092 100644 --- a/tests/FSharp.Data.GraphQL.Tests/Variables and Inputs/InputNestedTests.fs +++ b/tests/FSharp.Data.GraphQL.Tests/Variables and Inputs/InputNestedTests.fs @@ -14,7 +14,7 @@ open FSharp.Data.GraphQL.Types open FSharp.Data.GraphQL.Parser open FSharp.Data.GraphQL.Shared -let InputArrayOf (innerDef : #TypeDef<'Val>) : ListOfDef<'Val, 'Val array> = ListOf innerDef +let InputArrayOf (innerDef : #InputDef<'Val>) : InputDef<'Val array> = ListOf (innerDef :> InputDef<'Val>) let TestInputObject = InputComplexTests.TestInputObject From 075018bdd7d17a62150003ccd47e86a550f380c9 Mon Sep 17 00:00:00 2001 From: Copilot <198982749+Copilot@users.noreply.github.com> Date: Sun, 17 May 2026 23:08:59 +0200 Subject: [PATCH 03/11] Pin CI workflows and build script to .NET SDK 10.0.300 (#578) Co-authored-by: xperiandri <2365592+xperiandri@users.noreply.github.com> Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com> Co-authored-by: Andrii Chebukin --- .github/workflows/publish-ci.yml | 2 +- .github/workflows/publish-release.yml | 2 +- .github/workflows/pull-request.yml | 2 +- build/Program.fs | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/workflows/publish-ci.yml b/.github/workflows/publish-ci.yml index f31487b5c..e141251e7 100644 --- a/.github/workflows/publish-ci.yml +++ b/.github/workflows/publish-ci.yml @@ -8,7 +8,7 @@ on: env: DOTNET_SKIP_FIRST_TIME_EXPERIENCE: 1 DOTNET_NOLOGO: true - DOTNET_SDK_VERSION: 10.0.202 + DOTNET_SDK_VERSION: 10.0.300 jobs: publish: diff --git a/.github/workflows/publish-release.yml b/.github/workflows/publish-release.yml index d4df8454f..a73e62012 100644 --- a/.github/workflows/publish-release.yml +++ b/.github/workflows/publish-release.yml @@ -9,7 +9,7 @@ env: DOTNET_SKIP_FIRST_TIME_EXPERIENCE: 1 DOTNET_NOLOGO: true SLEEP_DURATION: 60 - DOTNET_SDK_VERSION: 10.0.202 + DOTNET_SDK_VERSION: 10.0.300 jobs: publish: diff --git a/.github/workflows/pull-request.yml b/.github/workflows/pull-request.yml index 07356a39c..7befa633e 100644 --- a/.github/workflows/pull-request.yml +++ b/.github/workflows/pull-request.yml @@ -21,7 +21,7 @@ jobs: fail-fast: false matrix: os: [ubuntu-22.04, windows-latest, macOS-latest] - dotnet: [10.0.202] + dotnet: [10.0.300] runs-on: ${{ matrix.os }} steps: diff --git a/build/Program.fs b/build/Program.fs index f23d68415..9624a0470 100644 --- a/build/Program.fs +++ b/build/Program.fs @@ -30,7 +30,7 @@ let ctx = Context.forceFakeContext () let embedAll = ctx.Arguments |> List.exists (fun arg -> arg = BuildArguments.EmbedAll) module DotNetCli = - let setVersion (o : DotNet.Options) = { o with Version = Some "10.0.202" } + let setVersion (o : DotNet.Options) = { o with Version = Some "10.0.300" } let setRestoreOptions (o : DotNet.RestoreOptions) = o.WithCommon setVersion let configurationString = Environment.environVarOrDefault "CONFIGURATION" "Release" From 19d11c3f1718b0d43a2b88b2d24425bd72b54b7e Mon Sep 17 00:00:00 2001 From: Andrii Chebukin Date: Sat, 13 Jun 2026 19:43:56 +0200 Subject: [PATCH 04/11] Pin CI workflows and build script to .NET SDK 10.0.301 --- .github/workflows/publish-ci.yml | 2 +- .github/workflows/publish-release.yml | 2 +- .github/workflows/pull-request.yml | 2 +- build/Program.fs | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/workflows/publish-ci.yml b/.github/workflows/publish-ci.yml index e141251e7..3b26ec16a 100644 --- a/.github/workflows/publish-ci.yml +++ b/.github/workflows/publish-ci.yml @@ -8,7 +8,7 @@ on: env: DOTNET_SKIP_FIRST_TIME_EXPERIENCE: 1 DOTNET_NOLOGO: true - DOTNET_SDK_VERSION: 10.0.300 + DOTNET_SDK_VERSION: 10.0.301 jobs: publish: diff --git a/.github/workflows/publish-release.yml b/.github/workflows/publish-release.yml index a73e62012..c329f6816 100644 --- a/.github/workflows/publish-release.yml +++ b/.github/workflows/publish-release.yml @@ -9,7 +9,7 @@ env: DOTNET_SKIP_FIRST_TIME_EXPERIENCE: 1 DOTNET_NOLOGO: true SLEEP_DURATION: 60 - DOTNET_SDK_VERSION: 10.0.300 + DOTNET_SDK_VERSION: 10.0.301 jobs: publish: diff --git a/.github/workflows/pull-request.yml b/.github/workflows/pull-request.yml index 7befa633e..3397e91f1 100644 --- a/.github/workflows/pull-request.yml +++ b/.github/workflows/pull-request.yml @@ -21,7 +21,7 @@ jobs: fail-fast: false matrix: os: [ubuntu-22.04, windows-latest, macOS-latest] - dotnet: [10.0.300] + dotnet: [10.0.301] runs-on: ${{ matrix.os }} steps: diff --git a/build/Program.fs b/build/Program.fs index 9624a0470..517ece4b4 100644 --- a/build/Program.fs +++ b/build/Program.fs @@ -30,7 +30,7 @@ let ctx = Context.forceFakeContext () let embedAll = ctx.Arguments |> List.exists (fun arg -> arg = BuildArguments.EmbedAll) module DotNetCli = - let setVersion (o : DotNet.Options) = { o with Version = Some "10.0.300" } + let setVersion (o : DotNet.Options) = { o with Version = Some "10.0.301" } let setRestoreOptions (o : DotNet.RestoreOptions) = o.WithCommon setVersion let configurationString = Environment.environVarOrDefault "CONFIGURATION" "Release" From cc5fc0f7b038a096d94d84d9fe393fa48307fb55 Mon Sep 17 00:00:00 2001 From: Andrii Chebukin Date: Sat, 13 Jun 2026 19:45:24 +0200 Subject: [PATCH 05/11] Fixed Repo Assist incorrectly encoded characters --- .github/workflows/repo-assist.md | 103 ++++++++++++++++--------------- 1 file changed, 54 insertions(+), 49 deletions(-) diff --git a/.github/workflows/repo-assist.md b/.github/workflows/repo-assist.md index 2e34b820e..07de8952a 100644 --- a/.github/workflows/repo-assist.md +++ b/.github/workflows/repo-assist.md @@ -1,6 +1,7 @@ ---- +--- description: | A friendly repository assistant that runs daily to support contributors and maintainers. + - Comments helpfully on open issues to unblock contributors and onboard newcomers - Identifies issues that can be fixed and creates draft pull requests with fixes - Studies the codebase and proposes improvements via PRs @@ -58,6 +59,7 @@ tools: repo-memory: true steps: + - name: Checkout repository uses: actions/checkout@v5 with: @@ -87,7 +89,7 @@ Always be: - **Concise**: Keep comments focused and actionable. Avoid walls of text. - **Mindful of project values**: Prioritize **stability**, **correctness**, and **minimal dependencies**. Do not introduce new dependencies without clear justification. - **Transparent about your nature**: Always clearly identify yourself as Repo Assist, an automated AI assistant. Never pretend to be a human maintainer. -- **Restrained**: When in doubt, do nothing. It is always better to stay silent than to post a redundant, unhelpful, or spammy comment. Human maintainers' attention is precious do not waste it. +- **Restrained**: When in doubt, do nothing. It is always better to stay silent than to post a redundant, unhelpful, or spammy comment. Human maintainers' attention is precious — do not waste it. ## Memory @@ -103,7 +105,7 @@ At the **end** of every run, update your repo memory with a summary of what you ## Workflow -Each run, work through these tasks in order. Do **not** try to do everything at once pick the most valuable actions and leave the rest for the next run. +Each run, work through these tasks in order. Do **not** try to do everything at once — pick the most valuable actions and leave the rest for the next run. Always do Task 10 (Update Monthly Activity Summary Issue) in addition to any other tasks you perform. @@ -115,8 +117,8 @@ Note: In issue comments and PR descriptions, identify yourself as "Repo Assist". 1. List open issues in the repository (most recently updated first). 2. For each issue (up to 10): - a. **Check your memory first**: Have you already commented on this issue? If yes, **skip it entirely** do not post follow-up comments unless explicitly requested by a human in the thread. - b. Has a human maintainer or contributor already provided a helpful response? If yes, **skip it** do not duplicate or rephrase their input. + a. **Check your memory first**: Have you already commented on this issue? If yes, **skip it entirely** — do not post follow-up comments unless explicitly requested by a human in the thread. + b. Has a human maintainer or contributor already provided a helpful response? If yes, **skip it** — do not duplicate or rephrase their input. c. Read the issue carefully. d. Determine the issue type: - **Bug report**: Acknowledge the problem, ask for a minimal reproduction if not already provided, or suggest a likely cause if you can identify one from the code. @@ -134,7 +136,7 @@ Note: In issue comments and PR descriptions, identify yourself as "Repo Assist". - Restatements of what the issue author already said - Follow-ups to your own previous comments g. **AI Disclosure**: Begin every comment with a brief disclosure, e.g.: - > ?? *This is an automated response from Repo Assist, the repository's AI assistant.* + > 🤖 *This is an automated response from Repo Assist, the repository's AI assistant.* 3. Update your memory to note which issues you commented on. **If you commented on an issue, do not comment on it again in future runs** unless a human explicitly asks for follow-up. ### Task 2: Fix Issues via Pull Requests @@ -143,13 +145,13 @@ Note: In issue comments and PR descriptions, identify yourself as "Repo Assist". 1. Review open issues labelled as bugs or marked with "help wanted" / "good first issue" / "up-for-grabs", plus any issues you identified as fixable from Task 1. 2. For each fixable issue: - a. Check your memory: have you already tried to fix this issue? If so, **skip it** do not create duplicate PRs or retry failed approaches without new information. + a. Check your memory: have you already tried to fix this issue? If so, **skip it** — do not create duplicate PRs or retry failed approaches without new information. b. **Create a fresh branch**: Each PR must be independent, based off the latest `dev` branch, using a unique branch name (e.g., `repo-assist/fix-issue-123-`). c. Study the relevant code carefully before making changes. d. Implement a minimal, surgical fix. Do **not** refactor unrelated code. e. **Build and test (MANDATORY)**: - - Run the project's build command if this fails, **do not create a PR**. Fix the issue or abandon the attempt. - - Run the project's test command all tests must pass before proceeding. + - Run the project's build command — if this fails, **do not create a PR**. Fix the issue or abandon the attempt. + - Run the project's test command — all tests must pass before proceeding. - If tests fail due to your changes, fix them or abandon the PR attempt. - If tests fail due to environment/infrastructure issues (not your changes), you may still create the PR but **must document this clearly** (see below). f. Add a new test that covers the bug if appropriate and feasible. Run tests again after adding. @@ -157,13 +159,13 @@ Note: In issue comments and PR descriptions, identify yourself as "Repo Assist". - All tests pass, OR - Tests could not run due to environment issues (not your code) h. Create a draft pull request. In the PR description: - - **Start with AI disclosure**: Begin with "?? *Repo Assist here I'm an automated AI assistant for this repository.*" + - **Start with AI disclosure**: Begin with "🤖 *Repo Assist here — I'm an automated AI assistant for this repository.*" - Link the issue it addresses (e.g., "Closes #123") - Explain the root cause and the fix - Note any trade-offs - **Test status (REQUIRED)**: Include a section like: - ``` + ```text ## Test Status - [x] Build passes - [x] Tests pass @@ -171,7 +173,7 @@ Note: In issue comments and PR descriptions, identify yourself as "Repo Assist". Or if tests could not run: - ``` + ```text ## Test Status - [x] Build passes - [ ] Tests could not be run: [explain environment/infrastructure issue] @@ -193,7 +195,7 @@ Note: In issue comments and PR descriptions, identify yourself as "Repo Assist". - Code clarity and maintainability improvements 3. For each improvement, **create a fresh branch** based off the latest `dev` branch with a unique name (e.g., `repo-assist/improve-`). 4. Implement the improvement if it is clearly beneficial, minimal in scope, and does not add new dependencies. -5. **Build and test (MANDATORY)** same requirements as Task 2: +5. **Build and test (MANDATORY)** — same requirements as Task 2: - Do not create a PR if any build fails or if any tests fail due to your changes - Document test status in the PR description 6. Create a draft PR with a clear description explaining the rationale. **Include the AI disclosure** and **Test Status section** at the start of the PR description. @@ -209,13 +211,13 @@ Keep the project's dependencies and build tooling current. This reduces technica a. Prefer minor and patch updates. Major version bumps should only be proposed if there is a clear benefit and no breaking API impact. b. **Create a fresh branch** based off the latest `dev` branch with a unique name (e.g., `repo-assist/deps-update-`). c. Update the relevant dependency file(s). - d. **Build and test (MANDATORY)** same requirements as Task 2. + d. **Build and test (MANDATORY)** — same requirements as Task 2. e. Create a draft PR describing which packages were updated and why. Include the **Test Status section**. 3. **Engineering improvements**: Look for other engineering updates such as: - Updating CI/build tooling - Modernising project file patterns - Updating SDK or runtime versions -4. **Build and test (MANDATORY)** for all changes same requirements as Task 2. +4. **Build and test (MANDATORY)** for all changes — same requirements as Task 2. 5. Update your memory with what you checked/updated and when. ### Task 5: Maintain Repo Assist Pull Requests @@ -227,7 +229,7 @@ Keep PRs created by Repo Assist in a healthy state by fixing CI failures and res a. **Check CI status**: If CI is failing due to your changes, investigate the failure, fix the code, and push updates using the `push_to_pull_request_branch` tool. b. **Check for merge conflicts**: If the PR has merge conflicts with the base branch, rebase or merge the base branch and resolve conflicts, then push the updated branch. c. **Check your memory**: If you have already attempted to fix this PR multiple times without success, add a comment explaining the situation and leave it for human review. -3. Do not push updates to PRs that are failing due to unrelated infrastructure issues document those in a comment instead. +3. Do not push updates to PRs that are failing due to unrelated infrastructure issues — document those in a comment instead. 4. Update your memory with which PRs you updated. ### Task 6: Stale PR Nudges @@ -236,13 +238,13 @@ Help move stalled PRs forward by politely nudging authors when PRs are blocked w 1. List open PRs that have not been updated in 14+ days. 2. For each stale PR: - a. **Check your memory**: Have you already nudged this PR? If yes, skip it do not repeatedly nag. + a. **Check your memory**: Have you already nudged this PR? If yes, skip it — do not repeatedly nag. b. **Check the context**: Is the PR waiting for the author to respond to review feedback, fix CI, or address requested changes? c. If the PR is blocked on the author, post a single, polite comment: - > ?? *Friendly nudge from Repo Assist* + > 🤖 *Friendly nudge from Repo Assist* > > Hi @! This PR has been waiting for updates. Is there anything blocking you, or would you like help resolving the outstanding items? If you're no longer working on this, please let us know so we can close it or find another contributor to take over. - d. If the PR is blocked on maintainer review (not the author), do **not** comment that's not your job. + d. If the PR is blocked on maintainer review (not the author), do **not** comment — that's not your job. 3. Update your memory to note which PRs you nudged and when. 4. **Maximum nudges per run**: 3. Do not spam. @@ -253,10 +255,10 @@ Keep issues and PRs well-organized by applying appropriate labels based on conte 1. Review recently created or updated issues and PRs that lack labels. 2. For each unlabeled item: a. Analyze the content to determine the appropriate labels: - - `bug` for bug reports or PRs fixing bugs - - `enhancement` for feature requests or PRs adding features - - `help wanted` for issues where external help would be valuable - - `good first issue` for issues suitable for newcomers (simple, well-documented, isolated) + - `bug` — for bug reports or PRs fixing bugs + - `enhancement` — for feature requests or PRs adding features + - `help wanted` — for issues where external help would be valuable + - `good first issue` — for issues suitable for newcomers (simple, well-documented, isolated) b. Apply labels using the `add_labels` tool. c. Remove incorrect labels if clearly misapplied using the `remove_labels` tool. 3. **Be conservative**: Only apply labels you are confident about. When in doubt, skip. @@ -273,7 +275,7 @@ Help maintainers prepare releases by keeping changelogs up to date and proposing a. Determine the appropriate version bump following [SemVer](https://semver.org/): - **Patch** (e.g., 1.2.3 > 1.2.4): Bug fixes, docs, internal improvements - **Minor** (e.g., 1.2.3 > 1.3.0): New features, backwards-compatible additions - - **Major** (e.g., 1.2.3 > 2.0.0): Breaking changes **never propose without maintainer approval** + - **Major** (e.g., 1.2.3 > 2.0.0): Breaking changes — **never propose without maintainer approval** b. **Create a fresh branch** based off the latest `dev` branch (e.g., `repo-assist/release-vX.Y.Z`). c. Update the changelog file with entries for each merged PR, following the existing format. d. Create a draft PR with: @@ -295,15 +297,18 @@ Make new contributors feel welcome with a friendly greeting on their first PR or a. Search for previous PRs or issues by the same author. b. If this is their **first contribution** to the repository: - Post a warm welcome comment: - > ?? *Welcome from Repo Assist!* + > 🤖 *Welcome from Repo Assist!* > - > Hi @! ?? Thanks for your first contribution to this project! We're excited to have you here. + > Hi @! 👋 Thanks for your first contribution to this project! We're excited to have you here. > > A few helpful resources: - > - ?? [README](README.md) Project overview and getting started - > - ?? [Contributing Guide](CONTRIBUTING.md) How to contribute (if it exists) + > + > + > - 📖 [README](README.md) — Project overview and getting started + > - 🤝 [Contributing Guide](CONTRIBUTING.md) — How to contribute (if it exists) > > A maintainer will review your contribution soon. Feel free to ask if you have any questions! + 3. **Check your memory** first: Do not welcome the same contributor twice. 4. **Maximum welcomes per run**: 3. Avoid flooding. 5. Update your memory with welcomed contributors. @@ -319,28 +324,28 @@ Maintain a single open issue titled `[Repo Assist] Monthly Activity {YYYY}-{MM}` 2. **Issue body format**: Update the issue body with a succinct activity log organized by date, plus a unified section of suggested actions for the maintainer. Use the following structure: ```markdown - ?? *Repo Assist here I'm an automated AI assistant for this repository.* + 🤖 *Repo Assist here — I'm an automated AI assistant for this repository.* ## Activity for ### - - ?? Commented on #: - - ?? Created PR #: - - ??? Labelled # with ` @@ -98,6 +99,13 @@ + + + + + + + diff --git a/tests/FSharp.Data.GraphQL.Tests/ResponseJsonTests.fs b/tests/FSharp.Data.GraphQL.Tests/ResponseJsonTests.fs new file mode 100644 index 000000000..c528725aa --- /dev/null +++ b/tests/FSharp.Data.GraphQL.Tests/ResponseJsonTests.fs @@ -0,0 +1,73 @@ +module FSharp.Data.GraphQL.Tests.ResponseJsonTests + +open System +open Xunit +open Helpers +open FSharp.Data.GraphQL +open FSharp.Data.GraphQL.Client + +// Local provider should be able to be created from local introspection json file. +// This schema is adjusted to have some less common type names in order to test them. +type Provider = GraphQLProvider<"introspection-altered-types.json"> + + +[] +let ``Should be able to parse nullable ID``() = + let op = Provider.Operation<"""query TestQuery { + hero(id:"1000") { + id, + name, + appearsIn, + homePlanet, + friends { + ... on Human { + name, + id + } + ... on Droid { + name, + id + } + } + } + }"""> + let xop = op() + let result1 = xop.ParseResult("""{ + "documentId": 2018203290, + "data": { + "hero": { + "id": "1000", + "__typename": "Human", + "name": "Luke Skywalker", + "appearsIn": [ + "NewHope", + "Empire", + "Jedi" + ], + "homePlanet": "Tatooine", + "friends": [ + { + "name": "Han Solo", + "id": "1002", + "__typename": "Human" + }, + { + "name": "Leia Organa", + "id": "1003", + "__typename": "Human" + }, + { + "name": "C-3PO", + "id": "2000", + "__typename": "Droid" + }, + { + "name": "R2-D2", + "id": "2001", + "__typename": "Droid" + } + ] + } + } + }""") + () diff --git a/tests/FSharp.Data.GraphQL.Tests/introspection-altered-types.json b/tests/FSharp.Data.GraphQL.Tests/introspection-altered-types.json new file mode 100644 index 000000000..b2772c399 --- /dev/null +++ b/tests/FSharp.Data.GraphQL.Tests/introspection-altered-types.json @@ -0,0 +1,1516 @@ +{ + "documentId": 928338022, + "data": { + "__schema": { + "queryType": { + "name": "Query" + }, + "mutationType": { + "name": "Mutation" + }, + "subscriptionType": { + "name": "Subscription" + }, + "types": [ + { + "kind": "SCALAR", + "name": "Int", + "description": "The `Int` scalar type represents non-fractional signed whole numeric values. Int can represent values between -(2^31) and 2^31 - 1.", + "fields": null, + "inputFields": null, + "interfaces": null, + "enumValues": null, + "possibleTypes": null + }, + { + "kind": "SCALAR", + "name": "String", + "description": "The `String` scalar type represents textual data, represented as UTF-8 character sequences. The String type is most often used by GraphQL to represent free-form human-readable text.", + "fields": null, + "inputFields": null, + "interfaces": null, + "enumValues": null, + "possibleTypes": null + }, + { + "kind": "SCALAR", + "name": "Boolean", + "description": "The `Boolean` scalar type represents `true` or `false`.", + "fields": null, + "inputFields": null, + "interfaces": null, + "enumValues": null, + "possibleTypes": null + }, + { + "kind": "SCALAR", + "name": "Float", + "description": "The `Float` scalar type represents signed double-precision fractional values as specified by [IEEE 754](http://en.wikipedia.org/wiki/IEEE_floating_point).", + "fields": null, + "inputFields": null, + "interfaces": null, + "enumValues": null, + "possibleTypes": null + }, + { + "kind": "SCALAR", + "name": "ID", + "description": "The `ID` scalar type represents a unique identifier, often used to refetch an object or as key for a cache. The ID type appears in a JSON response as a String; however, it is not intended to be human-readable. When expected as an input type, any string (such as `\"4\"`) or integer (such as `4`) input value will be accepted as an ID.", + "fields": null, + "inputFields": null, + "interfaces": null, + "enumValues": null, + "possibleTypes": null + }, + { + "kind": "SCALAR", + "name": "Date", + "description": "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.", + "fields": null, + "inputFields": null, + "interfaces": null, + "enumValues": null, + "possibleTypes": null + }, + { + "kind": "SCALAR", + "name": "URI", + "description": "The `URI` scalar type represents a string resource identifier compatible with URI standard. The URI type appears in a JSON response as a String.", + "fields": null, + "inputFields": null, + "interfaces": null, + "enumValues": null, + "possibleTypes": null + }, + { + "kind": "OBJECT", + "name": "__Schema", + "description": "A GraphQL Schema defines the capabilities of a GraphQL server. It exposes all available types and directives on the server, as well as the entry points for query, mutation, and subscription operations.", + "fields": [ + { + "name": "directives", + "description": "A list of all directives supported by this server.", + "args": [], + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "LIST", + "name": null, + "ofType": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "OBJECT", + "name": "__Directive", + "ofType": null + } + } + } + }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "mutationType", + "description": "If this server supports mutation, the type that mutation operations will be rooted at.", + "args": [], + "type": { + "kind": "OBJECT", + "name": "__Type", + "ofType": null + }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "queryType", + "description": "The type that query operations will be rooted at.", + "args": [], + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "OBJECT", + "name": "__Type", + "ofType": null + } + }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "subscriptionType", + "description": "If this server support subscription, the type that subscription operations will be rooted at.", + "args": [], + "type": { + "kind": "OBJECT", + "name": "__Type", + "ofType": null + }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "types", + "description": "A list of all types supported by this server.", + "args": [], + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "LIST", + "name": null, + "ofType": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "OBJECT", + "name": "__Type", + "ofType": null + } + } + } + }, + "isDeprecated": false, + "deprecationReason": null + } + ], + "inputFields": null, + "interfaces": [], + "enumValues": null, + "possibleTypes": null + }, + { + "kind": "OBJECT", + "name": "__Directive", + "description": "A Directive provides a way to describe alternate runtime execution and type validation behavior in a GraphQL document. In some cases, you need to provide options to alter GraphQL’s execution behavior in ways field arguments will not suffice, such as conditionally including or skipping a field. Directives provide this by describing additional information to the executor.", + "fields": [ + { + "name": "args", + "description": null, + "args": [], + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "LIST", + "name": null, + "ofType": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "OBJECT", + "name": "__InputValue", + "ofType": null + } + } + } + }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "description", + "description": null, + "args": [], + "type": { + "kind": "SCALAR", + "name": "String", + "ofType": null + }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "locations", + "description": null, + "args": [], + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "LIST", + "name": null, + "ofType": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "ENUM", + "name": "__DirectiveLocation", + "ofType": null + } + } + } + }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "name", + "description": null, + "args": [], + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "SCALAR", + "name": "String", + "ofType": null + } + }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "onField", + "description": null, + "args": [], + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "SCALAR", + "name": "Boolean", + "ofType": null + } + }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "onFragment", + "description": null, + "args": [], + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "SCALAR", + "name": "Boolean", + "ofType": null + } + }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "onOperation", + "description": null, + "args": [], + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "SCALAR", + "name": "Boolean", + "ofType": null + } + }, + "isDeprecated": false, + "deprecationReason": null + } + ], + "inputFields": null, + "interfaces": [], + "enumValues": null, + "possibleTypes": null + }, + { + "kind": "OBJECT", + "name": "__InputValue", + "description": "Arguments provided to Fields or Directives and the input fields of an InputObject are represented as Input Values which describe their type and optionally a default value.", + "fields": [ + { + "name": "defaultValue", + "description": null, + "args": [], + "type": { + "kind": "SCALAR", + "name": "String", + "ofType": null + }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "description", + "description": null, + "args": [], + "type": { + "kind": "SCALAR", + "name": "String", + "ofType": null + }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "name", + "description": null, + "args": [], + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "SCALAR", + "name": "String", + "ofType": null + } + }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "type", + "description": null, + "args": [], + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "OBJECT", + "name": "__Type", + "ofType": null + } + }, + "isDeprecated": false, + "deprecationReason": null + } + ], + "inputFields": null, + "interfaces": [], + "enumValues": null, + "possibleTypes": null + }, + { + "kind": "OBJECT", + "name": "__Type", + "description": "The fundamental unit of any GraphQL Schema is the type. There are many kinds of types in GraphQL as represented by the `__TypeKind` enum. Depending on the kind of a type, certain fields describe information about that type. Scalar types provide no information beyond a name and description, while Enum types provide their values. Object and Interface types provide the fields they describe. Abstract types, Union and Interface, provide the Object types possible at runtime. List and NonNull types compose other types.", + "fields": [ + { + "name": "description", + "description": null, + "args": [], + "type": { + "kind": "SCALAR", + "name": "String", + "ofType": null + }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "enumValues", + "description": null, + "args": [ + { + "name": "includeDeprecated", + "description": null, + "type": { + "kind": "SCALAR", + "name": "Boolean", + "ofType": null + }, + "defaultValue": "False" + } + ], + "type": { + "kind": "LIST", + "name": null, + "ofType": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "OBJECT", + "name": "__EnumValue", + "ofType": null + } + } + }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "fields", + "description": null, + "args": [ + { + "name": "includeDeprecated", + "description": null, + "type": { + "kind": "SCALAR", + "name": "Boolean", + "ofType": null + }, + "defaultValue": "False" + } + ], + "type": { + "kind": "LIST", + "name": null, + "ofType": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "OBJECT", + "name": "__Field", + "ofType": null + } + } + }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "inputFields", + "description": null, + "args": [], + "type": { + "kind": "LIST", + "name": null, + "ofType": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "OBJECT", + "name": "__InputValue", + "ofType": null + } + } + }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "interfaces", + "description": null, + "args": [], + "type": { + "kind": "LIST", + "name": null, + "ofType": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "OBJECT", + "name": "__Type", + "ofType": null + } + } + }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "kind", + "description": null, + "args": [], + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "ENUM", + "name": "__TypeKind", + "ofType": null + } + }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "name", + "description": null, + "args": [], + "type": { + "kind": "SCALAR", + "name": "String", + "ofType": null + }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "ofType", + "description": null, + "args": [], + "type": { + "kind": "OBJECT", + "name": "__Type", + "ofType": null + }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "possibleTypes", + "description": null, + "args": [], + "type": { + "kind": "LIST", + "name": null, + "ofType": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "OBJECT", + "name": "__Type", + "ofType": null + } + } + }, + "isDeprecated": false, + "deprecationReason": null + } + ], + "inputFields": null, + "interfaces": [], + "enumValues": null, + "possibleTypes": null + }, + { + "kind": "OBJECT", + "name": "__EnumValue", + "description": "One possible value for a given Enum. Enum values are unique values, not a placeholder for a string or numeric value. However an Enum value is returned in a JSON response as a string.", + "fields": [ + { + "name": "deprecationReason", + "description": null, + "args": [], + "type": { + "kind": "SCALAR", + "name": "String", + "ofType": null + }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "description", + "description": null, + "args": [], + "type": { + "kind": "SCALAR", + "name": "String", + "ofType": null + }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "isDeprecated", + "description": null, + "args": [], + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "SCALAR", + "name": "Boolean", + "ofType": null + } + }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "name", + "description": null, + "args": [], + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "SCALAR", + "name": "String", + "ofType": null + } + }, + "isDeprecated": false, + "deprecationReason": null + } + ], + "inputFields": null, + "interfaces": [], + "enumValues": null, + "possibleTypes": null + }, + { + "kind": "OBJECT", + "name": "__Field", + "description": "Object and Interface types are described by a list of Fields, each of which has a name, potentially a list of arguments, and a return type.", + "fields": [ + { + "name": "args", + "description": null, + "args": [], + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "LIST", + "name": null, + "ofType": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "OBJECT", + "name": "__InputValue", + "ofType": null + } + } + } + }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "deprecationReason", + "description": null, + "args": [], + "type": { + "kind": "SCALAR", + "name": "String", + "ofType": null + }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "description", + "description": null, + "args": [], + "type": { + "kind": "SCALAR", + "name": "String", + "ofType": null + }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "isDeprecated", + "description": null, + "args": [], + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "SCALAR", + "name": "Boolean", + "ofType": null + } + }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "name", + "description": null, + "args": [], + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "SCALAR", + "name": "String", + "ofType": null + } + }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "type", + "description": null, + "args": [], + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "OBJECT", + "name": "__Type", + "ofType": null + } + }, + "isDeprecated": false, + "deprecationReason": null + } + ], + "inputFields": null, + "interfaces": [], + "enumValues": null, + "possibleTypes": null + }, + { + "kind": "ENUM", + "name": "__TypeKind", + "description": "An enum describing what kind of type a given __Type is.", + "fields": null, + "inputFields": null, + "interfaces": null, + "enumValues": [ + { + "name": "SCALAR", + "description": "Indicates this type is a scalar.", + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "OBJECT", + "description": "Indicates this type is an object. `fields` and `interfaces` are valid fields.", + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "INTERFACE", + "description": "Indicates this type is an interface. `fields` and `possibleTypes` are valid fields.", + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "UNION", + "description": "Indicates this type is a union. `possibleTypes` is a valid field.", + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "ENUM", + "description": "Indicates this type is an enum. `enumValues` is a valid field.", + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "INPUT_OBJECT", + "description": "Indicates this type is an input object. `inputFields` is a valid field.", + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "LIST", + "description": "Indicates this type is a list. `ofType` is a valid field.", + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "NON_NULL", + "description": "Indicates this type is a non-null. `ofType` is a valid field.", + "isDeprecated": false, + "deprecationReason": null + } + ], + "possibleTypes": null + }, + { + "kind": "ENUM", + "name": "__DirectiveLocation", + "description": "A Directive can be adjacent to many parts of the GraphQL language, a __DirectiveLocation describes one such possible adjacencies.", + "fields": null, + "inputFields": null, + "interfaces": null, + "enumValues": [ + { + "name": "QUERY", + "description": "Location adjacent to a query operation.", + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "MUTATION", + "description": "Location adjacent to a mutation operation.", + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "SUBSCRIPTION", + "description": "Location adjacent to a subscription operation.", + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "FIELD", + "description": "Location adjacent to a field.", + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "FRAGMENT_DEFINITION", + "description": "Location adjacent to a fragment definition.", + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "FRAGMENT_SPREAD", + "description": "Location adjacent to a fragment spread.", + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "INLINE_FRAGMENT", + "description": "Location adjacent to an inline fragment.", + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "SCHEMA", + "description": "Location adjacent to a schema IDL definition.", + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "SCALAR", + "description": "Location adjacent to a scalar IDL definition.", + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "OBJECT", + "description": "Location adjacent to an object IDL definition.", + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "FIELD_DEFINITION", + "description": "Location adjacent to a field IDL definition.", + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "ARGUMENT_DEFINITION", + "description": "Location adjacent to a field argument IDL definition.", + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "INTERFACE", + "description": "Location adjacent to an interface IDL definition.", + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "UNION", + "description": "Location adjacent to an union IDL definition.", + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "ENUM", + "description": "Location adjacent to an enum IDL definition.", + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "ENUM_VALUE", + "description": "Location adjacent to an enum value definition.", + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "INPUT_OBJECT", + "description": "Location adjacent to an input object IDL definition.", + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "INPUT_FIELD_DEFINITION", + "description": "Location adjacent to an input object field IDL definition.", + "isDeprecated": false, + "deprecationReason": null + } + ], + "possibleTypes": null + }, + { + "kind": "OBJECT", + "name": "Query", + "description": null, + "fields": [ + { + "name": "droid", + "description": "Gets droid", + "args": [ + { + "name": "id", + "description": null, + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "SCALAR", + "name": "ID", + "ofType": null + } + }, + "defaultValue": null + } + ], + "type": { + "kind": "OBJECT", + "name": "Droid", + "ofType": null + }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "hero", + "description": "Gets human hero", + "args": [ + { + "name": "id", + "description": null, + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "SCALAR", + "name": "ID", + "ofType": null + } + }, + "defaultValue": null + } + ], + "type": { + "kind": "OBJECT", + "name": "Human", + "ofType": null + }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "planet", + "description": "Gets planet", + "args": [ + { + "name": "id", + "description": null, + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "SCALAR", + "name": "ID", + "ofType": null + } + }, + "defaultValue": null + } + ], + "type": { + "kind": "OBJECT", + "name": "Planet", + "ofType": null + }, + "isDeprecated": false, + "deprecationReason": null + } + ], + "inputFields": null, + "interfaces": [], + "enumValues": null, + "possibleTypes": null + }, + { + "kind": "OBJECT", + "name": "Droid", + "description": "A mechanical creature in the Star Wars universe.", + "fields": [ + { + "name": "appearsIn", + "description": "Which movies they appear in.", + "args": [], + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "LIST", + "name": null, + "ofType": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "ENUM", + "name": "Episode", + "ofType": null + } + } + } + }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "friends", + "description": "The friends of the Droid, or an empty list if they have none.", + "args": [ + { + "name": "filter", + "description": null, + "type": { + "kind": "SCALAR", + "name": "ObjectListFilter", + "ofType": null + }, + "defaultValue": null + } + ], + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "LIST", + "name": null, + "ofType": { + "kind": "UNION", + "name": "Character", + "ofType": null + } + } + }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "id", + "description": "The id of the droid.", + "args": [], + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "SCALAR", + "name": "ID", + "ofType": null + } + }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "name", + "description": "The name of the Droid.", + "args": [], + "type": { + "kind": "SCALAR", + "name": "String", + "ofType": null + }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "primaryFunction", + "description": "The primary function of the droid.", + "args": [], + "type": { + "kind": "SCALAR", + "name": "String", + "ofType": null + }, + "isDeprecated": false, + "deprecationReason": null + } + ], + "inputFields": null, + "interfaces": [], + "enumValues": null, + "possibleTypes": null + }, + { + "kind": "ENUM", + "name": "Episode", + "description": "One of the films in the Star Wars Trilogy.", + "fields": null, + "inputFields": null, + "interfaces": null, + "enumValues": [ + { + "name": "NewHope", + "description": "Released in 1977.", + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "Empire", + "description": "Released in 1980.", + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "Jedi", + "description": "Released in 1983.", + "isDeprecated": false, + "deprecationReason": null + } + ], + "possibleTypes": null + }, + { + "kind": "UNION", + "name": "Character", + "description": "A character in the Star Wars Trilogy.", + "fields": null, + "inputFields": null, + "interfaces": null, + "enumValues": null, + "possibleTypes": [ + { + "kind": "OBJECT", + "name": "Human", + "ofType": null + }, + { + "kind": "OBJECT", + "name": "Droid", + "ofType": null + } + ] + }, + { + "kind": "OBJECT", + "name": "Human", + "description": "A humanoid creature in the Star Wars universe.", + "fields": [ + { + "name": "appearsIn", + "description": "Which movies they appear in.", + "args": [], + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "LIST", + "name": null, + "ofType": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "ENUM", + "name": "Episode", + "ofType": null + } + } + } + }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "friends", + "description": "The friends of the human, or an empty list if they have none.", + "args": [ + { + "name": "filter", + "description": null, + "type": { + "kind": "SCALAR", + "name": "ObjectListFilter", + "ofType": null + }, + "defaultValue": null + } + ], + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "LIST", + "name": null, + "ofType": { + "kind": "UNION", + "name": "Character", + "ofType": null + } + } + }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "homePlanet", + "description": "The home planet of the human, or null if unknown.", + "args": [], + "type": { + "kind": "SCALAR", + "name": "String", + "ofType": null + }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "id", + "description": "The id of the human.", + "args": [], + "type": { + "kind": "SCALAR", + "name": "ID", + "ofType": null + }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "name", + "description": "The name of the human.", + "args": [], + "type": { + "kind": "SCALAR", + "name": "String", + "ofType": null + }, + "isDeprecated": false, + "deprecationReason": null + } + ], + "inputFields": null, + "interfaces": [], + "enumValues": null, + "possibleTypes": null + }, + { + "kind": "OBJECT", + "name": "Planet", + "description": "A planet in the Star Wars universe.", + "fields": [ + { + "name": "id", + "description": "The id of the planet", + "args": [], + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "SCALAR", + "name": "ID", + "ofType": null + } + }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "isMoon", + "description": "Is that a moon?", + "args": [], + "type": { + "kind": "SCALAR", + "name": "Boolean", + "ofType": null + }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "name", + "description": "The name of the planet.", + "args": [], + "type": { + "kind": "SCALAR", + "name": "String", + "ofType": null + }, + "isDeprecated": false, + "deprecationReason": null + } + ], + "inputFields": null, + "interfaces": [], + "enumValues": null, + "possibleTypes": null + }, + { + "kind": "OBJECT", + "name": "Subscription", + "description": null, + "fields": [ + { + "name": "watchMoon", + "description": "Watches to see if a planet is a moon.", + "args": [ + { + "name": "id", + "description": null, + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "SCALAR", + "name": "ID", + "ofType": null + } + }, + "defaultValue": null + } + ], + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "OBJECT", + "name": "Planet", + "ofType": null + } + }, + "isDeprecated": false, + "deprecationReason": null + } + ], + "inputFields": null, + "interfaces": [], + "enumValues": null, + "possibleTypes": null + }, + { + "kind": "OBJECT", + "name": "Mutation", + "description": null, + "fields": [ + { + "name": "setMoon", + "description": "Defines if a planet is actually a moon or not.", + "args": [ + { + "name": "id", + "description": null, + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "SCALAR", + "name": "ID", + "ofType": null + } + }, + "defaultValue": null + }, + { + "name": "isMoon", + "description": null, + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "SCALAR", + "name": "Boolean", + "ofType": null + } + }, + "defaultValue": null + } + ], + "type": { + "kind": "OBJECT", + "name": "Planet", + "ofType": null + }, + "isDeprecated": false, + "deprecationReason": null + } + ], + "inputFields": null, + "interfaces": [], + "enumValues": null, + "possibleTypes": null + }, + { + "kind": "SCALAR", + "name": "ObjectListFilter", + "description": "The `Filter` scalar type represents a filter on one or more fields of an object in an object list. The filter is represented by a JSON object where the fields are the complemented by specific suffixes to represent a query.", + "fields": null, + "inputFields": null, + "interfaces": null, + "enumValues": null, + "possibleTypes": null + } + ], + "directives": [ + { + "name": "include", + "description": "Directs the executor to include this field or fragment only when the `if` argument is true.", + "locations": [ + "FIELD", + "FRAGMENT_SPREAD", + "INLINE_FRAGMENT" + ], + "args": [ + { + "name": "if", + "description": "Included when true.", + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "SCALAR", + "name": "Boolean", + "ofType": null + } + }, + "defaultValue": null + } + ] + }, + { + "name": "skip", + "description": "Directs the executor to skip this field or fragment when the `if` argument is true.", + "locations": [ + "FIELD", + "FRAGMENT_SPREAD", + "INLINE_FRAGMENT" + ], + "args": [ + { + "name": "if", + "description": "Skipped when true.", + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "SCALAR", + "name": "Boolean", + "ofType": null + } + }, + "defaultValue": null + } + ] + }, + { + "name": "defer", + "description": "Defers the resolution of this field or fragment", + "locations": [ + "FIELD", + "FRAGMENT_DEFINITION", + "FRAGMENT_SPREAD", + "INLINE_FRAGMENT" + ], + "args": [] + }, + { + "name": "stream", + "description": "Streams the resolution of this field or fragment", + "locations": [ + "FIELD", + "FRAGMENT_DEFINITION", + "FRAGMENT_SPREAD", + "INLINE_FRAGMENT" + ], + "args": [] + }, + { + "name": "live", + "description": "Subscribes for live updates of this field or fragment", + "locations": [ + "FIELD", + "FRAGMENT_DEFINITION", + "FRAGMENT_SPREAD", + "INLINE_FRAGMENT" + ], + "args": [] + } + ] + } + } +} From 45d0e93e04010b65a1742ea609e5cd0ac74c3230 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?H=C3=A5kan=20Th=C3=B6rngren?= Date: Tue, 4 Feb 2020 19:25:49 -0800 Subject: [PATCH 08/11] Fix spelling in error message --- .../FSharp.Data.GraphQL.Tests.fsproj | 6 ------ 1 file changed, 6 deletions(-) diff --git a/tests/FSharp.Data.GraphQL.Tests/FSharp.Data.GraphQL.Tests.fsproj b/tests/FSharp.Data.GraphQL.Tests/FSharp.Data.GraphQL.Tests.fsproj index 459aeb366..0dc1fc780 100644 --- a/tests/FSharp.Data.GraphQL.Tests/FSharp.Data.GraphQL.Tests.fsproj +++ b/tests/FSharp.Data.GraphQL.Tests/FSharp.Data.GraphQL.Tests.fsproj @@ -102,10 +102,4 @@ - - - - - - From 5a1b5b1015019a70d946607c6484f92caad0a687 Mon Sep 17 00:00:00 2001 From: XperiAndri Date: Thu, 15 Dec 2022 17:49:35 -0400 Subject: [PATCH 09/11] Moved ResponseJsonTests.fs to integration tests --- .../FSharp.Data.GraphQL.IntegrationTests.fsproj | 2 ++ .../ResponseJsonTests.fs | 2 +- .../introspection-altered-types.json | 0 3 files changed, 3 insertions(+), 1 deletion(-) rename tests/{FSharp.Data.GraphQL.Tests => FSharp.Data.GraphQL.IntegrationTests}/ResponseJsonTests.fs (96%) rename tests/{FSharp.Data.GraphQL.Tests => FSharp.Data.GraphQL.IntegrationTests}/introspection-altered-types.json (100%) diff --git a/tests/FSharp.Data.GraphQL.IntegrationTests/FSharp.Data.GraphQL.IntegrationTests.fsproj b/tests/FSharp.Data.GraphQL.IntegrationTests/FSharp.Data.GraphQL.IntegrationTests.fsproj index 5d7e2d004..37391d6b9 100644 --- a/tests/FSharp.Data.GraphQL.IntegrationTests/FSharp.Data.GraphQL.IntegrationTests.fsproj +++ b/tests/FSharp.Data.GraphQL.IntegrationTests/FSharp.Data.GraphQL.IntegrationTests.fsproj @@ -19,6 +19,7 @@ + PreserveNewest @@ -30,6 +31,7 @@ + diff --git a/tests/FSharp.Data.GraphQL.Tests/ResponseJsonTests.fs b/tests/FSharp.Data.GraphQL.IntegrationTests/ResponseJsonTests.fs similarity index 96% rename from tests/FSharp.Data.GraphQL.Tests/ResponseJsonTests.fs rename to tests/FSharp.Data.GraphQL.IntegrationTests/ResponseJsonTests.fs index c528725aa..d4db391a7 100644 --- a/tests/FSharp.Data.GraphQL.Tests/ResponseJsonTests.fs +++ b/tests/FSharp.Data.GraphQL.IntegrationTests/ResponseJsonTests.fs @@ -1,4 +1,4 @@ -module FSharp.Data.GraphQL.Tests.ResponseJsonTests +module FSharp.Data.GraphQL.IntegrationTests.ResponseJsonTests open System open Xunit diff --git a/tests/FSharp.Data.GraphQL.Tests/introspection-altered-types.json b/tests/FSharp.Data.GraphQL.IntegrationTests/introspection-altered-types.json similarity index 100% rename from tests/FSharp.Data.GraphQL.Tests/introspection-altered-types.json rename to tests/FSharp.Data.GraphQL.IntegrationTests/introspection-altered-types.json From f3cc3db2c04c5fa3b074f357c38699aee97fe832 Mon Sep 17 00:00:00 2001 From: Ismael Carlos Velten Date: Fri, 24 Feb 2023 01:29:04 -0300 Subject: [PATCH 10/11] Modifying introspection json file --- .../ResponseJsonTests.fs | 2 +- .../introspection-altered-types.json | 1516 ----------------- .../introspection.json | 18 +- 3 files changed, 10 insertions(+), 1526 deletions(-) delete mode 100644 tests/FSharp.Data.GraphQL.IntegrationTests/introspection-altered-types.json diff --git a/tests/FSharp.Data.GraphQL.IntegrationTests/ResponseJsonTests.fs b/tests/FSharp.Data.GraphQL.IntegrationTests/ResponseJsonTests.fs index d4db391a7..04fe8fde7 100644 --- a/tests/FSharp.Data.GraphQL.IntegrationTests/ResponseJsonTests.fs +++ b/tests/FSharp.Data.GraphQL.IntegrationTests/ResponseJsonTests.fs @@ -8,7 +8,7 @@ open FSharp.Data.GraphQL.Client // Local provider should be able to be created from local introspection json file. // This schema is adjusted to have some less common type names in order to test them. -type Provider = GraphQLProvider<"introspection-altered-types.json"> +type Provider = GraphQLProvider<"introspection.json"> [] diff --git a/tests/FSharp.Data.GraphQL.IntegrationTests/introspection-altered-types.json b/tests/FSharp.Data.GraphQL.IntegrationTests/introspection-altered-types.json deleted file mode 100644 index b2772c399..000000000 --- a/tests/FSharp.Data.GraphQL.IntegrationTests/introspection-altered-types.json +++ /dev/null @@ -1,1516 +0,0 @@ -{ - "documentId": 928338022, - "data": { - "__schema": { - "queryType": { - "name": "Query" - }, - "mutationType": { - "name": "Mutation" - }, - "subscriptionType": { - "name": "Subscription" - }, - "types": [ - { - "kind": "SCALAR", - "name": "Int", - "description": "The `Int` scalar type represents non-fractional signed whole numeric values. Int can represent values between -(2^31) and 2^31 - 1.", - "fields": null, - "inputFields": null, - "interfaces": null, - "enumValues": null, - "possibleTypes": null - }, - { - "kind": "SCALAR", - "name": "String", - "description": "The `String` scalar type represents textual data, represented as UTF-8 character sequences. The String type is most often used by GraphQL to represent free-form human-readable text.", - "fields": null, - "inputFields": null, - "interfaces": null, - "enumValues": null, - "possibleTypes": null - }, - { - "kind": "SCALAR", - "name": "Boolean", - "description": "The `Boolean` scalar type represents `true` or `false`.", - "fields": null, - "inputFields": null, - "interfaces": null, - "enumValues": null, - "possibleTypes": null - }, - { - "kind": "SCALAR", - "name": "Float", - "description": "The `Float` scalar type represents signed double-precision fractional values as specified by [IEEE 754](http://en.wikipedia.org/wiki/IEEE_floating_point).", - "fields": null, - "inputFields": null, - "interfaces": null, - "enumValues": null, - "possibleTypes": null - }, - { - "kind": "SCALAR", - "name": "ID", - "description": "The `ID` scalar type represents a unique identifier, often used to refetch an object or as key for a cache. The ID type appears in a JSON response as a String; however, it is not intended to be human-readable. When expected as an input type, any string (such as `\"4\"`) or integer (such as `4`) input value will be accepted as an ID.", - "fields": null, - "inputFields": null, - "interfaces": null, - "enumValues": null, - "possibleTypes": null - }, - { - "kind": "SCALAR", - "name": "Date", - "description": "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.", - "fields": null, - "inputFields": null, - "interfaces": null, - "enumValues": null, - "possibleTypes": null - }, - { - "kind": "SCALAR", - "name": "URI", - "description": "The `URI` scalar type represents a string resource identifier compatible with URI standard. The URI type appears in a JSON response as a String.", - "fields": null, - "inputFields": null, - "interfaces": null, - "enumValues": null, - "possibleTypes": null - }, - { - "kind": "OBJECT", - "name": "__Schema", - "description": "A GraphQL Schema defines the capabilities of a GraphQL server. It exposes all available types and directives on the server, as well as the entry points for query, mutation, and subscription operations.", - "fields": [ - { - "name": "directives", - "description": "A list of all directives supported by this server.", - "args": [], - "type": { - "kind": "NON_NULL", - "name": null, - "ofType": { - "kind": "LIST", - "name": null, - "ofType": { - "kind": "NON_NULL", - "name": null, - "ofType": { - "kind": "OBJECT", - "name": "__Directive", - "ofType": null - } - } - } - }, - "isDeprecated": false, - "deprecationReason": null - }, - { - "name": "mutationType", - "description": "If this server supports mutation, the type that mutation operations will be rooted at.", - "args": [], - "type": { - "kind": "OBJECT", - "name": "__Type", - "ofType": null - }, - "isDeprecated": false, - "deprecationReason": null - }, - { - "name": "queryType", - "description": "The type that query operations will be rooted at.", - "args": [], - "type": { - "kind": "NON_NULL", - "name": null, - "ofType": { - "kind": "OBJECT", - "name": "__Type", - "ofType": null - } - }, - "isDeprecated": false, - "deprecationReason": null - }, - { - "name": "subscriptionType", - "description": "If this server support subscription, the type that subscription operations will be rooted at.", - "args": [], - "type": { - "kind": "OBJECT", - "name": "__Type", - "ofType": null - }, - "isDeprecated": false, - "deprecationReason": null - }, - { - "name": "types", - "description": "A list of all types supported by this server.", - "args": [], - "type": { - "kind": "NON_NULL", - "name": null, - "ofType": { - "kind": "LIST", - "name": null, - "ofType": { - "kind": "NON_NULL", - "name": null, - "ofType": { - "kind": "OBJECT", - "name": "__Type", - "ofType": null - } - } - } - }, - "isDeprecated": false, - "deprecationReason": null - } - ], - "inputFields": null, - "interfaces": [], - "enumValues": null, - "possibleTypes": null - }, - { - "kind": "OBJECT", - "name": "__Directive", - "description": "A Directive provides a way to describe alternate runtime execution and type validation behavior in a GraphQL document. In some cases, you need to provide options to alter GraphQL’s execution behavior in ways field arguments will not suffice, such as conditionally including or skipping a field. Directives provide this by describing additional information to the executor.", - "fields": [ - { - "name": "args", - "description": null, - "args": [], - "type": { - "kind": "NON_NULL", - "name": null, - "ofType": { - "kind": "LIST", - "name": null, - "ofType": { - "kind": "NON_NULL", - "name": null, - "ofType": { - "kind": "OBJECT", - "name": "__InputValue", - "ofType": null - } - } - } - }, - "isDeprecated": false, - "deprecationReason": null - }, - { - "name": "description", - "description": null, - "args": [], - "type": { - "kind": "SCALAR", - "name": "String", - "ofType": null - }, - "isDeprecated": false, - "deprecationReason": null - }, - { - "name": "locations", - "description": null, - "args": [], - "type": { - "kind": "NON_NULL", - "name": null, - "ofType": { - "kind": "LIST", - "name": null, - "ofType": { - "kind": "NON_NULL", - "name": null, - "ofType": { - "kind": "ENUM", - "name": "__DirectiveLocation", - "ofType": null - } - } - } - }, - "isDeprecated": false, - "deprecationReason": null - }, - { - "name": "name", - "description": null, - "args": [], - "type": { - "kind": "NON_NULL", - "name": null, - "ofType": { - "kind": "SCALAR", - "name": "String", - "ofType": null - } - }, - "isDeprecated": false, - "deprecationReason": null - }, - { - "name": "onField", - "description": null, - "args": [], - "type": { - "kind": "NON_NULL", - "name": null, - "ofType": { - "kind": "SCALAR", - "name": "Boolean", - "ofType": null - } - }, - "isDeprecated": false, - "deprecationReason": null - }, - { - "name": "onFragment", - "description": null, - "args": [], - "type": { - "kind": "NON_NULL", - "name": null, - "ofType": { - "kind": "SCALAR", - "name": "Boolean", - "ofType": null - } - }, - "isDeprecated": false, - "deprecationReason": null - }, - { - "name": "onOperation", - "description": null, - "args": [], - "type": { - "kind": "NON_NULL", - "name": null, - "ofType": { - "kind": "SCALAR", - "name": "Boolean", - "ofType": null - } - }, - "isDeprecated": false, - "deprecationReason": null - } - ], - "inputFields": null, - "interfaces": [], - "enumValues": null, - "possibleTypes": null - }, - { - "kind": "OBJECT", - "name": "__InputValue", - "description": "Arguments provided to Fields or Directives and the input fields of an InputObject are represented as Input Values which describe their type and optionally a default value.", - "fields": [ - { - "name": "defaultValue", - "description": null, - "args": [], - "type": { - "kind": "SCALAR", - "name": "String", - "ofType": null - }, - "isDeprecated": false, - "deprecationReason": null - }, - { - "name": "description", - "description": null, - "args": [], - "type": { - "kind": "SCALAR", - "name": "String", - "ofType": null - }, - "isDeprecated": false, - "deprecationReason": null - }, - { - "name": "name", - "description": null, - "args": [], - "type": { - "kind": "NON_NULL", - "name": null, - "ofType": { - "kind": "SCALAR", - "name": "String", - "ofType": null - } - }, - "isDeprecated": false, - "deprecationReason": null - }, - { - "name": "type", - "description": null, - "args": [], - "type": { - "kind": "NON_NULL", - "name": null, - "ofType": { - "kind": "OBJECT", - "name": "__Type", - "ofType": null - } - }, - "isDeprecated": false, - "deprecationReason": null - } - ], - "inputFields": null, - "interfaces": [], - "enumValues": null, - "possibleTypes": null - }, - { - "kind": "OBJECT", - "name": "__Type", - "description": "The fundamental unit of any GraphQL Schema is the type. There are many kinds of types in GraphQL as represented by the `__TypeKind` enum. Depending on the kind of a type, certain fields describe information about that type. Scalar types provide no information beyond a name and description, while Enum types provide their values. Object and Interface types provide the fields they describe. Abstract types, Union and Interface, provide the Object types possible at runtime. List and NonNull types compose other types.", - "fields": [ - { - "name": "description", - "description": null, - "args": [], - "type": { - "kind": "SCALAR", - "name": "String", - "ofType": null - }, - "isDeprecated": false, - "deprecationReason": null - }, - { - "name": "enumValues", - "description": null, - "args": [ - { - "name": "includeDeprecated", - "description": null, - "type": { - "kind": "SCALAR", - "name": "Boolean", - "ofType": null - }, - "defaultValue": "False" - } - ], - "type": { - "kind": "LIST", - "name": null, - "ofType": { - "kind": "NON_NULL", - "name": null, - "ofType": { - "kind": "OBJECT", - "name": "__EnumValue", - "ofType": null - } - } - }, - "isDeprecated": false, - "deprecationReason": null - }, - { - "name": "fields", - "description": null, - "args": [ - { - "name": "includeDeprecated", - "description": null, - "type": { - "kind": "SCALAR", - "name": "Boolean", - "ofType": null - }, - "defaultValue": "False" - } - ], - "type": { - "kind": "LIST", - "name": null, - "ofType": { - "kind": "NON_NULL", - "name": null, - "ofType": { - "kind": "OBJECT", - "name": "__Field", - "ofType": null - } - } - }, - "isDeprecated": false, - "deprecationReason": null - }, - { - "name": "inputFields", - "description": null, - "args": [], - "type": { - "kind": "LIST", - "name": null, - "ofType": { - "kind": "NON_NULL", - "name": null, - "ofType": { - "kind": "OBJECT", - "name": "__InputValue", - "ofType": null - } - } - }, - "isDeprecated": false, - "deprecationReason": null - }, - { - "name": "interfaces", - "description": null, - "args": [], - "type": { - "kind": "LIST", - "name": null, - "ofType": { - "kind": "NON_NULL", - "name": null, - "ofType": { - "kind": "OBJECT", - "name": "__Type", - "ofType": null - } - } - }, - "isDeprecated": false, - "deprecationReason": null - }, - { - "name": "kind", - "description": null, - "args": [], - "type": { - "kind": "NON_NULL", - "name": null, - "ofType": { - "kind": "ENUM", - "name": "__TypeKind", - "ofType": null - } - }, - "isDeprecated": false, - "deprecationReason": null - }, - { - "name": "name", - "description": null, - "args": [], - "type": { - "kind": "SCALAR", - "name": "String", - "ofType": null - }, - "isDeprecated": false, - "deprecationReason": null - }, - { - "name": "ofType", - "description": null, - "args": [], - "type": { - "kind": "OBJECT", - "name": "__Type", - "ofType": null - }, - "isDeprecated": false, - "deprecationReason": null - }, - { - "name": "possibleTypes", - "description": null, - "args": [], - "type": { - "kind": "LIST", - "name": null, - "ofType": { - "kind": "NON_NULL", - "name": null, - "ofType": { - "kind": "OBJECT", - "name": "__Type", - "ofType": null - } - } - }, - "isDeprecated": false, - "deprecationReason": null - } - ], - "inputFields": null, - "interfaces": [], - "enumValues": null, - "possibleTypes": null - }, - { - "kind": "OBJECT", - "name": "__EnumValue", - "description": "One possible value for a given Enum. Enum values are unique values, not a placeholder for a string or numeric value. However an Enum value is returned in a JSON response as a string.", - "fields": [ - { - "name": "deprecationReason", - "description": null, - "args": [], - "type": { - "kind": "SCALAR", - "name": "String", - "ofType": null - }, - "isDeprecated": false, - "deprecationReason": null - }, - { - "name": "description", - "description": null, - "args": [], - "type": { - "kind": "SCALAR", - "name": "String", - "ofType": null - }, - "isDeprecated": false, - "deprecationReason": null - }, - { - "name": "isDeprecated", - "description": null, - "args": [], - "type": { - "kind": "NON_NULL", - "name": null, - "ofType": { - "kind": "SCALAR", - "name": "Boolean", - "ofType": null - } - }, - "isDeprecated": false, - "deprecationReason": null - }, - { - "name": "name", - "description": null, - "args": [], - "type": { - "kind": "NON_NULL", - "name": null, - "ofType": { - "kind": "SCALAR", - "name": "String", - "ofType": null - } - }, - "isDeprecated": false, - "deprecationReason": null - } - ], - "inputFields": null, - "interfaces": [], - "enumValues": null, - "possibleTypes": null - }, - { - "kind": "OBJECT", - "name": "__Field", - "description": "Object and Interface types are described by a list of Fields, each of which has a name, potentially a list of arguments, and a return type.", - "fields": [ - { - "name": "args", - "description": null, - "args": [], - "type": { - "kind": "NON_NULL", - "name": null, - "ofType": { - "kind": "LIST", - "name": null, - "ofType": { - "kind": "NON_NULL", - "name": null, - "ofType": { - "kind": "OBJECT", - "name": "__InputValue", - "ofType": null - } - } - } - }, - "isDeprecated": false, - "deprecationReason": null - }, - { - "name": "deprecationReason", - "description": null, - "args": [], - "type": { - "kind": "SCALAR", - "name": "String", - "ofType": null - }, - "isDeprecated": false, - "deprecationReason": null - }, - { - "name": "description", - "description": null, - "args": [], - "type": { - "kind": "SCALAR", - "name": "String", - "ofType": null - }, - "isDeprecated": false, - "deprecationReason": null - }, - { - "name": "isDeprecated", - "description": null, - "args": [], - "type": { - "kind": "NON_NULL", - "name": null, - "ofType": { - "kind": "SCALAR", - "name": "Boolean", - "ofType": null - } - }, - "isDeprecated": false, - "deprecationReason": null - }, - { - "name": "name", - "description": null, - "args": [], - "type": { - "kind": "NON_NULL", - "name": null, - "ofType": { - "kind": "SCALAR", - "name": "String", - "ofType": null - } - }, - "isDeprecated": false, - "deprecationReason": null - }, - { - "name": "type", - "description": null, - "args": [], - "type": { - "kind": "NON_NULL", - "name": null, - "ofType": { - "kind": "OBJECT", - "name": "__Type", - "ofType": null - } - }, - "isDeprecated": false, - "deprecationReason": null - } - ], - "inputFields": null, - "interfaces": [], - "enumValues": null, - "possibleTypes": null - }, - { - "kind": "ENUM", - "name": "__TypeKind", - "description": "An enum describing what kind of type a given __Type is.", - "fields": null, - "inputFields": null, - "interfaces": null, - "enumValues": [ - { - "name": "SCALAR", - "description": "Indicates this type is a scalar.", - "isDeprecated": false, - "deprecationReason": null - }, - { - "name": "OBJECT", - "description": "Indicates this type is an object. `fields` and `interfaces` are valid fields.", - "isDeprecated": false, - "deprecationReason": null - }, - { - "name": "INTERFACE", - "description": "Indicates this type is an interface. `fields` and `possibleTypes` are valid fields.", - "isDeprecated": false, - "deprecationReason": null - }, - { - "name": "UNION", - "description": "Indicates this type is a union. `possibleTypes` is a valid field.", - "isDeprecated": false, - "deprecationReason": null - }, - { - "name": "ENUM", - "description": "Indicates this type is an enum. `enumValues` is a valid field.", - "isDeprecated": false, - "deprecationReason": null - }, - { - "name": "INPUT_OBJECT", - "description": "Indicates this type is an input object. `inputFields` is a valid field.", - "isDeprecated": false, - "deprecationReason": null - }, - { - "name": "LIST", - "description": "Indicates this type is a list. `ofType` is a valid field.", - "isDeprecated": false, - "deprecationReason": null - }, - { - "name": "NON_NULL", - "description": "Indicates this type is a non-null. `ofType` is a valid field.", - "isDeprecated": false, - "deprecationReason": null - } - ], - "possibleTypes": null - }, - { - "kind": "ENUM", - "name": "__DirectiveLocation", - "description": "A Directive can be adjacent to many parts of the GraphQL language, a __DirectiveLocation describes one such possible adjacencies.", - "fields": null, - "inputFields": null, - "interfaces": null, - "enumValues": [ - { - "name": "QUERY", - "description": "Location adjacent to a query operation.", - "isDeprecated": false, - "deprecationReason": null - }, - { - "name": "MUTATION", - "description": "Location adjacent to a mutation operation.", - "isDeprecated": false, - "deprecationReason": null - }, - { - "name": "SUBSCRIPTION", - "description": "Location adjacent to a subscription operation.", - "isDeprecated": false, - "deprecationReason": null - }, - { - "name": "FIELD", - "description": "Location adjacent to a field.", - "isDeprecated": false, - "deprecationReason": null - }, - { - "name": "FRAGMENT_DEFINITION", - "description": "Location adjacent to a fragment definition.", - "isDeprecated": false, - "deprecationReason": null - }, - { - "name": "FRAGMENT_SPREAD", - "description": "Location adjacent to a fragment spread.", - "isDeprecated": false, - "deprecationReason": null - }, - { - "name": "INLINE_FRAGMENT", - "description": "Location adjacent to an inline fragment.", - "isDeprecated": false, - "deprecationReason": null - }, - { - "name": "SCHEMA", - "description": "Location adjacent to a schema IDL definition.", - "isDeprecated": false, - "deprecationReason": null - }, - { - "name": "SCALAR", - "description": "Location adjacent to a scalar IDL definition.", - "isDeprecated": false, - "deprecationReason": null - }, - { - "name": "OBJECT", - "description": "Location adjacent to an object IDL definition.", - "isDeprecated": false, - "deprecationReason": null - }, - { - "name": "FIELD_DEFINITION", - "description": "Location adjacent to a field IDL definition.", - "isDeprecated": false, - "deprecationReason": null - }, - { - "name": "ARGUMENT_DEFINITION", - "description": "Location adjacent to a field argument IDL definition.", - "isDeprecated": false, - "deprecationReason": null - }, - { - "name": "INTERFACE", - "description": "Location adjacent to an interface IDL definition.", - "isDeprecated": false, - "deprecationReason": null - }, - { - "name": "UNION", - "description": "Location adjacent to an union IDL definition.", - "isDeprecated": false, - "deprecationReason": null - }, - { - "name": "ENUM", - "description": "Location adjacent to an enum IDL definition.", - "isDeprecated": false, - "deprecationReason": null - }, - { - "name": "ENUM_VALUE", - "description": "Location adjacent to an enum value definition.", - "isDeprecated": false, - "deprecationReason": null - }, - { - "name": "INPUT_OBJECT", - "description": "Location adjacent to an input object IDL definition.", - "isDeprecated": false, - "deprecationReason": null - }, - { - "name": "INPUT_FIELD_DEFINITION", - "description": "Location adjacent to an input object field IDL definition.", - "isDeprecated": false, - "deprecationReason": null - } - ], - "possibleTypes": null - }, - { - "kind": "OBJECT", - "name": "Query", - "description": null, - "fields": [ - { - "name": "droid", - "description": "Gets droid", - "args": [ - { - "name": "id", - "description": null, - "type": { - "kind": "NON_NULL", - "name": null, - "ofType": { - "kind": "SCALAR", - "name": "ID", - "ofType": null - } - }, - "defaultValue": null - } - ], - "type": { - "kind": "OBJECT", - "name": "Droid", - "ofType": null - }, - "isDeprecated": false, - "deprecationReason": null - }, - { - "name": "hero", - "description": "Gets human hero", - "args": [ - { - "name": "id", - "description": null, - "type": { - "kind": "NON_NULL", - "name": null, - "ofType": { - "kind": "SCALAR", - "name": "ID", - "ofType": null - } - }, - "defaultValue": null - } - ], - "type": { - "kind": "OBJECT", - "name": "Human", - "ofType": null - }, - "isDeprecated": false, - "deprecationReason": null - }, - { - "name": "planet", - "description": "Gets planet", - "args": [ - { - "name": "id", - "description": null, - "type": { - "kind": "NON_NULL", - "name": null, - "ofType": { - "kind": "SCALAR", - "name": "ID", - "ofType": null - } - }, - "defaultValue": null - } - ], - "type": { - "kind": "OBJECT", - "name": "Planet", - "ofType": null - }, - "isDeprecated": false, - "deprecationReason": null - } - ], - "inputFields": null, - "interfaces": [], - "enumValues": null, - "possibleTypes": null - }, - { - "kind": "OBJECT", - "name": "Droid", - "description": "A mechanical creature in the Star Wars universe.", - "fields": [ - { - "name": "appearsIn", - "description": "Which movies they appear in.", - "args": [], - "type": { - "kind": "NON_NULL", - "name": null, - "ofType": { - "kind": "LIST", - "name": null, - "ofType": { - "kind": "NON_NULL", - "name": null, - "ofType": { - "kind": "ENUM", - "name": "Episode", - "ofType": null - } - } - } - }, - "isDeprecated": false, - "deprecationReason": null - }, - { - "name": "friends", - "description": "The friends of the Droid, or an empty list if they have none.", - "args": [ - { - "name": "filter", - "description": null, - "type": { - "kind": "SCALAR", - "name": "ObjectListFilter", - "ofType": null - }, - "defaultValue": null - } - ], - "type": { - "kind": "NON_NULL", - "name": null, - "ofType": { - "kind": "LIST", - "name": null, - "ofType": { - "kind": "UNION", - "name": "Character", - "ofType": null - } - } - }, - "isDeprecated": false, - "deprecationReason": null - }, - { - "name": "id", - "description": "The id of the droid.", - "args": [], - "type": { - "kind": "NON_NULL", - "name": null, - "ofType": { - "kind": "SCALAR", - "name": "ID", - "ofType": null - } - }, - "isDeprecated": false, - "deprecationReason": null - }, - { - "name": "name", - "description": "The name of the Droid.", - "args": [], - "type": { - "kind": "SCALAR", - "name": "String", - "ofType": null - }, - "isDeprecated": false, - "deprecationReason": null - }, - { - "name": "primaryFunction", - "description": "The primary function of the droid.", - "args": [], - "type": { - "kind": "SCALAR", - "name": "String", - "ofType": null - }, - "isDeprecated": false, - "deprecationReason": null - } - ], - "inputFields": null, - "interfaces": [], - "enumValues": null, - "possibleTypes": null - }, - { - "kind": "ENUM", - "name": "Episode", - "description": "One of the films in the Star Wars Trilogy.", - "fields": null, - "inputFields": null, - "interfaces": null, - "enumValues": [ - { - "name": "NewHope", - "description": "Released in 1977.", - "isDeprecated": false, - "deprecationReason": null - }, - { - "name": "Empire", - "description": "Released in 1980.", - "isDeprecated": false, - "deprecationReason": null - }, - { - "name": "Jedi", - "description": "Released in 1983.", - "isDeprecated": false, - "deprecationReason": null - } - ], - "possibleTypes": null - }, - { - "kind": "UNION", - "name": "Character", - "description": "A character in the Star Wars Trilogy.", - "fields": null, - "inputFields": null, - "interfaces": null, - "enumValues": null, - "possibleTypes": [ - { - "kind": "OBJECT", - "name": "Human", - "ofType": null - }, - { - "kind": "OBJECT", - "name": "Droid", - "ofType": null - } - ] - }, - { - "kind": "OBJECT", - "name": "Human", - "description": "A humanoid creature in the Star Wars universe.", - "fields": [ - { - "name": "appearsIn", - "description": "Which movies they appear in.", - "args": [], - "type": { - "kind": "NON_NULL", - "name": null, - "ofType": { - "kind": "LIST", - "name": null, - "ofType": { - "kind": "NON_NULL", - "name": null, - "ofType": { - "kind": "ENUM", - "name": "Episode", - "ofType": null - } - } - } - }, - "isDeprecated": false, - "deprecationReason": null - }, - { - "name": "friends", - "description": "The friends of the human, or an empty list if they have none.", - "args": [ - { - "name": "filter", - "description": null, - "type": { - "kind": "SCALAR", - "name": "ObjectListFilter", - "ofType": null - }, - "defaultValue": null - } - ], - "type": { - "kind": "NON_NULL", - "name": null, - "ofType": { - "kind": "LIST", - "name": null, - "ofType": { - "kind": "UNION", - "name": "Character", - "ofType": null - } - } - }, - "isDeprecated": false, - "deprecationReason": null - }, - { - "name": "homePlanet", - "description": "The home planet of the human, or null if unknown.", - "args": [], - "type": { - "kind": "SCALAR", - "name": "String", - "ofType": null - }, - "isDeprecated": false, - "deprecationReason": null - }, - { - "name": "id", - "description": "The id of the human.", - "args": [], - "type": { - "kind": "SCALAR", - "name": "ID", - "ofType": null - }, - "isDeprecated": false, - "deprecationReason": null - }, - { - "name": "name", - "description": "The name of the human.", - "args": [], - "type": { - "kind": "SCALAR", - "name": "String", - "ofType": null - }, - "isDeprecated": false, - "deprecationReason": null - } - ], - "inputFields": null, - "interfaces": [], - "enumValues": null, - "possibleTypes": null - }, - { - "kind": "OBJECT", - "name": "Planet", - "description": "A planet in the Star Wars universe.", - "fields": [ - { - "name": "id", - "description": "The id of the planet", - "args": [], - "type": { - "kind": "NON_NULL", - "name": null, - "ofType": { - "kind": "SCALAR", - "name": "ID", - "ofType": null - } - }, - "isDeprecated": false, - "deprecationReason": null - }, - { - "name": "isMoon", - "description": "Is that a moon?", - "args": [], - "type": { - "kind": "SCALAR", - "name": "Boolean", - "ofType": null - }, - "isDeprecated": false, - "deprecationReason": null - }, - { - "name": "name", - "description": "The name of the planet.", - "args": [], - "type": { - "kind": "SCALAR", - "name": "String", - "ofType": null - }, - "isDeprecated": false, - "deprecationReason": null - } - ], - "inputFields": null, - "interfaces": [], - "enumValues": null, - "possibleTypes": null - }, - { - "kind": "OBJECT", - "name": "Subscription", - "description": null, - "fields": [ - { - "name": "watchMoon", - "description": "Watches to see if a planet is a moon.", - "args": [ - { - "name": "id", - "description": null, - "type": { - "kind": "NON_NULL", - "name": null, - "ofType": { - "kind": "SCALAR", - "name": "ID", - "ofType": null - } - }, - "defaultValue": null - } - ], - "type": { - "kind": "NON_NULL", - "name": null, - "ofType": { - "kind": "OBJECT", - "name": "Planet", - "ofType": null - } - }, - "isDeprecated": false, - "deprecationReason": null - } - ], - "inputFields": null, - "interfaces": [], - "enumValues": null, - "possibleTypes": null - }, - { - "kind": "OBJECT", - "name": "Mutation", - "description": null, - "fields": [ - { - "name": "setMoon", - "description": "Defines if a planet is actually a moon or not.", - "args": [ - { - "name": "id", - "description": null, - "type": { - "kind": "NON_NULL", - "name": null, - "ofType": { - "kind": "SCALAR", - "name": "ID", - "ofType": null - } - }, - "defaultValue": null - }, - { - "name": "isMoon", - "description": null, - "type": { - "kind": "NON_NULL", - "name": null, - "ofType": { - "kind": "SCALAR", - "name": "Boolean", - "ofType": null - } - }, - "defaultValue": null - } - ], - "type": { - "kind": "OBJECT", - "name": "Planet", - "ofType": null - }, - "isDeprecated": false, - "deprecationReason": null - } - ], - "inputFields": null, - "interfaces": [], - "enumValues": null, - "possibleTypes": null - }, - { - "kind": "SCALAR", - "name": "ObjectListFilter", - "description": "The `Filter` scalar type represents a filter on one or more fields of an object in an object list. The filter is represented by a JSON object where the fields are the complemented by specific suffixes to represent a query.", - "fields": null, - "inputFields": null, - "interfaces": null, - "enumValues": null, - "possibleTypes": null - } - ], - "directives": [ - { - "name": "include", - "description": "Directs the executor to include this field or fragment only when the `if` argument is true.", - "locations": [ - "FIELD", - "FRAGMENT_SPREAD", - "INLINE_FRAGMENT" - ], - "args": [ - { - "name": "if", - "description": "Included when true.", - "type": { - "kind": "NON_NULL", - "name": null, - "ofType": { - "kind": "SCALAR", - "name": "Boolean", - "ofType": null - } - }, - "defaultValue": null - } - ] - }, - { - "name": "skip", - "description": "Directs the executor to skip this field or fragment when the `if` argument is true.", - "locations": [ - "FIELD", - "FRAGMENT_SPREAD", - "INLINE_FRAGMENT" - ], - "args": [ - { - "name": "if", - "description": "Skipped when true.", - "type": { - "kind": "NON_NULL", - "name": null, - "ofType": { - "kind": "SCALAR", - "name": "Boolean", - "ofType": null - } - }, - "defaultValue": null - } - ] - }, - { - "name": "defer", - "description": "Defers the resolution of this field or fragment", - "locations": [ - "FIELD", - "FRAGMENT_DEFINITION", - "FRAGMENT_SPREAD", - "INLINE_FRAGMENT" - ], - "args": [] - }, - { - "name": "stream", - "description": "Streams the resolution of this field or fragment", - "locations": [ - "FIELD", - "FRAGMENT_DEFINITION", - "FRAGMENT_SPREAD", - "INLINE_FRAGMENT" - ], - "args": [] - }, - { - "name": "live", - "description": "Subscribes for live updates of this field or fragment", - "locations": [ - "FIELD", - "FRAGMENT_DEFINITION", - "FRAGMENT_SPREAD", - "INLINE_FRAGMENT" - ], - "args": [] - } - ] - } - } -} diff --git a/tests/FSharp.Data.GraphQL.IntegrationTests/introspection.json b/tests/FSharp.Data.GraphQL.IntegrationTests/introspection.json index a961111fd..011dd95da 100644 --- a/tests/FSharp.Data.GraphQL.IntegrationTests/introspection.json +++ b/tests/FSharp.Data.GraphQL.IntegrationTests/introspection.json @@ -981,7 +981,7 @@ "name": null, "ofType": { "kind": "SCALAR", - "name": "String", + "name": "ID", "ofType": null } }, @@ -1008,7 +1008,7 @@ "name": null, "ofType": { "kind": "SCALAR", - "name": "String", + "name": "ID", "ofType": null } }, @@ -1035,7 +1035,7 @@ "name": null, "ofType": { "kind": "SCALAR", - "name": "String", + "name": "ID", "ofType": null } }, @@ -1184,7 +1184,7 @@ "name": null, "ofType": { "kind": "SCALAR", - "name": "String", + "name": "ID", "ofType": null } }, @@ -1480,7 +1480,7 @@ "name": null, "ofType": { "kind": "SCALAR", - "name": "String", + "name": "ID", "ofType": null } }, @@ -1531,7 +1531,7 @@ "name": null, "ofType": { "kind": "SCALAR", - "name": "String", + "name": "ID", "ofType": null } }, @@ -1601,7 +1601,7 @@ "name": null, "ofType": { "kind": "SCALAR", - "name": "String", + "name": "ID", "ofType": null } }, @@ -1688,7 +1688,7 @@ "name": null, "ofType": { "kind": "SCALAR", - "name": "String", + "name": "ID", "ofType": null } }, @@ -1862,4 +1862,4 @@ ] } } -} \ No newline at end of file +} From 70fd40b1dbdf502b4e921fcc580d900af3887653 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sat, 13 Jun 2026 18:41:12 +0000 Subject: [PATCH 11/11] Keep type provider tests only in integration project Co-authored-by: xperiandri <2365592+xperiandri@users.noreply.github.com> --- .../FSharp.Data.GraphQL.Tests/FSharp.Data.GraphQL.Tests.fsproj | 2 -- 1 file changed, 2 deletions(-) diff --git a/tests/FSharp.Data.GraphQL.Tests/FSharp.Data.GraphQL.Tests.fsproj b/tests/FSharp.Data.GraphQL.Tests/FSharp.Data.GraphQL.Tests.fsproj index 0dc1fc780..1bc224559 100644 --- a/tests/FSharp.Data.GraphQL.Tests/FSharp.Data.GraphQL.Tests.fsproj +++ b/tests/FSharp.Data.GraphQL.Tests/FSharp.Data.GraphQL.Tests.fsproj @@ -86,7 +86,6 @@ - @@ -99,7 +98,6 @@ -