From 1ef478c63975d2f83bb5c572d737be1048cd446c Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sun, 17 May 2026 00:55:46 +0000 Subject: [PATCH 01/14] Initial plan Agent-Logs-Url: https://github.com/fsprojects/FSharp.Data.GraphQL/sessions/f7ecc729-e334-47b1-8577-9f252b9417f2 Co-authored-by: xperiandri <2365592+xperiandri@users.noreply.github.com> --- tests/FSharp.Data.GraphQL.IntegrationTests/introspection.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/FSharp.Data.GraphQL.IntegrationTests/introspection.json b/tests/FSharp.Data.GraphQL.IntegrationTests/introspection.json index 7b3d9abc..02f70cdf 100644 --- a/tests/FSharp.Data.GraphQL.IntegrationTests/introspection.json +++ b/tests/FSharp.Data.GraphQL.IntegrationTests/introspection.json @@ -1,5 +1,5 @@ { - "documentId": -128167532, + "documentId": 2027435736, "data": { "__schema": { "queryType": { From 23032d975b3b146a9874e8a582b7dfdfb8ac50ee Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sun, 17 May 2026 00:56:02 +0000 Subject: [PATCH 02/14] Revert unintended introspection baseline change Agent-Logs-Url: https://github.com/fsprojects/FSharp.Data.GraphQL/sessions/f7ecc729-e334-47b1-8577-9f252b9417f2 Co-authored-by: xperiandri <2365592+xperiandri@users.noreply.github.com> --- tests/FSharp.Data.GraphQL.IntegrationTests/introspection.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/FSharp.Data.GraphQL.IntegrationTests/introspection.json b/tests/FSharp.Data.GraphQL.IntegrationTests/introspection.json index 02f70cdf..7b3d9abc 100644 --- a/tests/FSharp.Data.GraphQL.IntegrationTests/introspection.json +++ b/tests/FSharp.Data.GraphQL.IntegrationTests/introspection.json @@ -1,5 +1,5 @@ { - "documentId": 2027435736, + "documentId": -128167532, "data": { "__schema": { "queryType": { From f32c5e7d7967b32fb978a9e72e2467d7b7fbdc2f Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sun, 17 May 2026 01:04:04 +0000 Subject: [PATCH 03/14] Support GraphQL error extensions in OperationError Agent-Logs-Url: https://github.com/fsprojects/FSharp.Data.GraphQL/sessions/f7ecc729-e334-47b1-8577-9f252b9417f2 Co-authored-by: xperiandri <2365592+xperiandri@users.noreply.github.com> --- src/FSharp.Data.GraphQL.Client/BaseTypes.fs | 58 +++++++++++++++++---- 1 file changed, 48 insertions(+), 10 deletions(-) diff --git a/src/FSharp.Data.GraphQL.Client/BaseTypes.fs b/src/FSharp.Data.GraphQL.Client/BaseTypes.fs index 5bc66444..fb4663e5 100644 --- a/src/FSharp.Data.GraphQL.Client/BaseTypes.fs +++ b/src/FSharp.Data.GraphQL.Client/BaseTypes.fs @@ -26,12 +26,23 @@ type SchemaFieldInfo = /// A type alias to represent a Type name. type TypeName = string +/// Contains data about a GQL operation error. +type OperationErrorLocation = + { /// The source line of the GraphQL operation document where the error occurred. + Line : int + /// The source column of the GraphQL operation document where the error occurred. + Column : int } + /// Contains data about a GQL operation error. type OperationError = { /// The description of the error that happened in the operation. Message : string + /// The source locations in the GraphQL operation document where the error occurred. + Locations : OperationErrorLocation [] /// The path to the field that produced the error while resolving its value. - Path : obj [] } + Path : obj [] + /// Extension data attached to the error. + Extensions : Map } /// Contains helpers to build HTTP header sequences to be used in GraphQLProvider Run methods. module HttpHeaders = @@ -394,17 +405,44 @@ module internal JsonValueHelper = |> Array.map (firstUpper >> mapFieldValue) let getErrors (errors : JsonValue []) = + let tryFindField fieldName (fields : (string * JsonValue) []) = + fields |> Array.tryFind (fun (name, _) -> name = fieldName) |> Option.map snd + + let parsePath = function + | Some (JsonValue.Array path) -> + let pathMapper = function + | JsonValue.String x -> box x + | JsonValue.Integer x -> box x + | _ -> failwith "Error parsing response errors. An item in the path is neither a String nor a Number." + path |> Array.map pathMapper + | Some JsonValue.Null | None -> [||] + | _ -> failwith "Error parsing response errors. Path field must be an Array." + + let parseLocations = function + | Some (JsonValue.Array locations) -> + let parseLocation = function + | JsonValue.Record locationFields -> + match tryFindField "line" locationFields, tryFindField "column" locationFields with + | Some (JsonValue.Integer line), Some (JsonValue.Integer column) -> { Line = line; Column = column } + | _ -> failwith "Error parsing response errors. A location item must contain Integer fields named \"line\" and \"column\"." + | _ -> failwith "Error parsing response errors. A location item is not a Record." + locations |> Array.map parseLocation + | Some JsonValue.Null | None -> [||] + | _ -> failwith "Error parsing response errors. Locations field must be an Array." + + let parseExtensions = function + | Some (JsonValue.Record fields) -> Serialization.deserializeMap fields + | Some JsonValue.Null | None -> Map.empty + | _ -> failwith "Error parsing response errors. Extensions field must be a Record." + let errorMapper = function | JsonValue.Record fields -> - match fields |> Array.tryFind (fun (name, _) -> name = "message"), fields |> Array.tryFind (fun (name, _) -> name = "path") with - | Some (_, JsonValue.String message), Some (_, JsonValue.Array path) -> - let pathMapper = function - | JsonValue.String x -> box x - | JsonValue.Integer x -> box x - | _ -> failwith "Error parsing response errors. A item in the path is neither a String or a Number." - { Message = message; Path = Array.map pathMapper path } - | Some (_, JsonValue.String message), None-> - { Message = message; Path = [||]} + match tryFindField "message" fields with + | Some (JsonValue.String message) -> + { Message = message + Locations = tryFindField "locations" fields |> parseLocations + Path = tryFindField "path" fields |> parsePath + Extensions = tryFindField "extensions" fields |> parseExtensions } | _ -> failwith "Error parsing response errors. Unsupported errors field format." | other -> failwithf "Error parsing response errors. Expected error to be a Record type, but it is %s." (other.ToString()) Array.map errorMapper errors From b3aac652afe36bf27b0dc2165d65eaef36017c98 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sun, 17 May 2026 01:04:59 +0000 Subject: [PATCH 04/14] Refine OperationError path parse diagnostics Agent-Logs-Url: https://github.com/fsprojects/FSharp.Data.GraphQL/sessions/f7ecc729-e334-47b1-8577-9f252b9417f2 Co-authored-by: xperiandri <2365592+xperiandri@users.noreply.github.com> --- src/FSharp.Data.GraphQL.Client/BaseTypes.fs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/FSharp.Data.GraphQL.Client/BaseTypes.fs b/src/FSharp.Data.GraphQL.Client/BaseTypes.fs index fb4663e5..d53608e0 100644 --- a/src/FSharp.Data.GraphQL.Client/BaseTypes.fs +++ b/src/FSharp.Data.GraphQL.Client/BaseTypes.fs @@ -413,7 +413,7 @@ module internal JsonValueHelper = let pathMapper = function | JsonValue.String x -> box x | JsonValue.Integer x -> box x - | _ -> failwith "Error parsing response errors. An item in the path is neither a String nor a Number." + | _ -> failwith "Error parsing response errors. An item in the path is neither a String nor an Integer." path |> Array.map pathMapper | Some JsonValue.Null | None -> [||] | _ -> failwith "Error parsing response errors. Path field must be an Array." From 38240f6c7b90a1ea077817a74648646191ff8ec0 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sun, 17 May 2026 01:28:18 +0000 Subject: [PATCH 05/14] Add OperationError unit and integration tests Agent-Logs-Url: https://github.com/fsprojects/FSharp.Data.GraphQL/sessions/d64ab4ee-eb67-4b7c-a114-d6ad4b640254 Co-authored-by: xperiandri <2365592+xperiandri@users.noreply.github.com> --- .../Schema.fs | 15 ++++- ...Sharp.Data.GraphQL.IntegrationTests.fsproj | 1 + .../OperationErrorTests.fs | 66 +++++++++++++++++++ 3 files changed, 81 insertions(+), 1 deletion(-) create mode 100644 tests/FSharp.Data.GraphQL.IntegrationTests/OperationErrorTests.fs diff --git a/tests/FSharp.Data.GraphQL.IntegrationTests.Server/Schema.fs b/tests/FSharp.Data.GraphQL.IntegrationTests.Server/Schema.fs index fec874b4..27baa73f 100644 --- a/tests/FSharp.Data.GraphQL.IntegrationTests.Server/Schema.fs +++ b/tests/FSharp.Data.GraphQL.IntegrationTests.Server/Schema.fs @@ -2,6 +2,7 @@ namespace FSharp.Data.GraphQL.Samples.StarWarsApi open System open System.Text +open System.Collections.Generic open Microsoft.AspNetCore.Http open Microsoft.Extensions.DependencyInjection @@ -135,7 +136,19 @@ module Schema = typedef = StructNullable OutputType, description = "Enters an input type and get it back.", args = [ Define.Input("input", Nullable InputType, description = "The input to be echoed as an output.") ], - resolve = fun ctx _ -> ctx.TryArg("input")) ]) + resolve = fun ctx _ -> ctx.TryArg("input")) + Define.Field( + name = "alwaysError", + typedef = Nullable StringType, + description = "Always produces an execution error for integration tests.", + args = [], + resolve = + fun _ _ -> + let extensions = Dictionary 2 + extensions["code"] <- box "OPERATION_ERROR_TEST" + extensions["severity"] <- box 7 + raise (GQLMessageException("Always fails for tests", extensions)) + ) ]) let InputFileObject = Define.InputObject( name = "InputFile", 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 44bc1d17..f133dee2 100644 --- a/tests/FSharp.Data.GraphQL.IntegrationTests/FSharp.Data.GraphQL.IntegrationTests.fsproj +++ b/tests/FSharp.Data.GraphQL.IntegrationTests/FSharp.Data.GraphQL.IntegrationTests.fsproj @@ -22,6 +22,7 @@ + diff --git a/tests/FSharp.Data.GraphQL.IntegrationTests/OperationErrorTests.fs b/tests/FSharp.Data.GraphQL.IntegrationTests/OperationErrorTests.fs new file mode 100644 index 00000000..059bb657 --- /dev/null +++ b/tests/FSharp.Data.GraphQL.IntegrationTests/OperationErrorTests.fs @@ -0,0 +1,66 @@ +module FSharp.Data.GraphQL.IntegrationTests.OperationErrorTests + +open System.Net.Http +open Xunit +open Helpers +open FSharp.Data.GraphQL +open FSharp.Data.GraphQL.Client + +let [] ServerUrl = "http://localhost:8085" + +type Provider = GraphQLProvider + +module ErrorOperation = + let operation = + Provider.Operation<"""query ErrorQuery { + alwaysError + }""">() + + type Operation = Provider.Operations.ErrorQuery + +[] +let ``Should parse operation error fields from raw response`` () = + let result = + OperationResultBase( + rawResponse = new HttpResponseMessage(), + responseJson = + JsonValue.Parse + """{ + "errors": [{ + "message": "unit-test error", + "path": ["alwaysError", 0], + "locations": [{ "line": 2, "column": 13 }], + "extensions": { "code": "UNIT_TEST", "retryable": false, "severity": 7 } + }] + }""", + operationFields = [||], + operationTypeName = "Query" + ) + + result.Errors.Length |> equals 1 + + let error : FSharp.Data.GraphQL.OperationError = result.Errors.[0] + error.Message |> equals "unit-test error" + error.Path |> equals [| box "alwaysError"; box 0 |] + error.Locations |> equals [| { Line = 2; Column = 13 } |] + error.Extensions.["code"] |> equals (box "UNIT_TEST") + error.Extensions.["retryable"] |> equals (box false) + error.Extensions.["severity"] |> equals (box 7) + +[] +let ``Should map server error extensions and locations into operation result`` () = + let result = ErrorOperation.operation.Run() + + result.Errors.Length |> equals 1 + + let error : FSharp.Data.GraphQL.OperationError = result.Errors.[0] + error.Message |> equals "Always fails for tests" + error.Path |> equals [| box "alwaysError" |] + + error.Locations |> equals [||] + + error.Extensions.ContainsKey "code" |> equals true + error.Extensions.["code"] |> equals (box "OPERATION_ERROR_TEST") + error.Extensions.ContainsKey "severity" |> equals true + error.Extensions.["severity"] |> equals (box 7) + error.Extensions.ContainsKey "kind" |> equals true From 78543715a5641341b96183933e863aa24a35e18b Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sun, 17 May 2026 01:32:58 +0000 Subject: [PATCH 06/14] Use initializer-style extensions in integration error resolver Agent-Logs-Url: https://github.com/fsprojects/FSharp.Data.GraphQL/sessions/d64ab4ee-eb67-4b7c-a114-d6ad4b640254 Co-authored-by: xperiandri <2365592+xperiandri@users.noreply.github.com> --- .../FSharp.Data.GraphQL.IntegrationTests.Server/Schema.fs | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/tests/FSharp.Data.GraphQL.IntegrationTests.Server/Schema.fs b/tests/FSharp.Data.GraphQL.IntegrationTests.Server/Schema.fs index 27baa73f..5c7fc87f 100644 --- a/tests/FSharp.Data.GraphQL.IntegrationTests.Server/Schema.fs +++ b/tests/FSharp.Data.GraphQL.IntegrationTests.Server/Schema.fs @@ -144,9 +144,11 @@ module Schema = args = [], resolve = fun _ _ -> - let extensions = Dictionary 2 - extensions["code"] <- box "OPERATION_ERROR_TEST" - extensions["severity"] <- box 7 + let extensions = + Dictionary( + [ KeyValuePair("code", box "OPERATION_ERROR_TEST") + KeyValuePair("severity", box 7) ] + ) raise (GQLMessageException("Always fails for tests", extensions)) ) ]) From aee71e3a12d724c2eb6a0a76358b0306cb404321 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sun, 17 May 2026 01:41:14 +0000 Subject: [PATCH 07/14] Strengthen integration assertion for error kind extension Agent-Logs-Url: https://github.com/fsprojects/FSharp.Data.GraphQL/sessions/d64ab4ee-eb67-4b7c-a114-d6ad4b640254 Co-authored-by: xperiandri <2365592+xperiandri@users.noreply.github.com> --- .../OperationErrorTests.fs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/tests/FSharp.Data.GraphQL.IntegrationTests/OperationErrorTests.fs b/tests/FSharp.Data.GraphQL.IntegrationTests/OperationErrorTests.fs index 059bb657..b2554448 100644 --- a/tests/FSharp.Data.GraphQL.IntegrationTests/OperationErrorTests.fs +++ b/tests/FSharp.Data.GraphQL.IntegrationTests/OperationErrorTests.fs @@ -64,3 +64,7 @@ let ``Should map server error extensions and locations into operation result`` ( error.Extensions.ContainsKey "severity" |> equals true error.Extensions.["severity"] |> equals (box 7) error.Extensions.ContainsKey "kind" |> equals true + match error.Extensions.["kind"] with + | :? string as kind -> kind |> equals "Execution" + | :? int as kind -> kind |> equals 3 + | kind -> failwithf "Unexpected kind extension value: %A" kind From 556e301cda97515094953e4741cc1b56eef7856b Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sun, 17 May 2026 13:01:57 +0000 Subject: [PATCH 08/14] Plan GraphQL spec links in XML docs Agent-Logs-Url: https://github.com/fsprojects/FSharp.Data.GraphQL/sessions/aabfb966-da13-41e9-821e-b097aa211948 Co-authored-by: xperiandri <2365592+xperiandri@users.noreply.github.com> --- tests/FSharp.Data.GraphQL.IntegrationTests/introspection.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/FSharp.Data.GraphQL.IntegrationTests/introspection.json b/tests/FSharp.Data.GraphQL.IntegrationTests/introspection.json index 7b3d9abc..9e468aa6 100644 --- a/tests/FSharp.Data.GraphQL.IntegrationTests/introspection.json +++ b/tests/FSharp.Data.GraphQL.IntegrationTests/introspection.json @@ -1,5 +1,5 @@ { - "documentId": -128167532, + "documentId": -353990577, "data": { "__schema": { "queryType": { From 57a2c8e57fdb531c583192c816b829b2175156d5 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sun, 17 May 2026 13:06:20 +0000 Subject: [PATCH 09/14] Add GraphQL spec section links to OperationError XML docs Agent-Logs-Url: https://github.com/fsprojects/FSharp.Data.GraphQL/sessions/aabfb966-da13-41e9-821e-b097aa211948 Co-authored-by: xperiandri <2365592+xperiandri@users.noreply.github.com> --- src/FSharp.Data.GraphQL.Client/BaseTypes.fs | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/src/FSharp.Data.GraphQL.Client/BaseTypes.fs b/src/FSharp.Data.GraphQL.Client/BaseTypes.fs index d53608e0..d447c84c 100644 --- a/src/FSharp.Data.GraphQL.Client/BaseTypes.fs +++ b/src/FSharp.Data.GraphQL.Client/BaseTypes.fs @@ -26,14 +26,18 @@ type SchemaFieldInfo = /// A type alias to represent a Type name. type TypeName = string -/// Contains data about a GQL operation error. +/// Contains source location information for a single GraphQL error location entry in the response. +/// See GraphQL specification sections and +/// . type OperationErrorLocation = { /// The source line of the GraphQL operation document where the error occurred. Line : int /// The source column of the GraphQL operation document where the error occurred. Column : int } -/// Contains data about a GQL operation error. +/// Contains data about a GraphQL operation error as defined by the GraphQL response format. +/// See GraphQL specification sections and +/// . type OperationError = { /// The description of the error that happened in the operation. Message : string @@ -475,7 +479,9 @@ type OperationResultBase (rawResponse: HttpResponseMessage, responseJson : JsonV [] member _.RawData = rawData - /// Gets all the errors returned by the operation on the server. + /// Gets all GraphQL errors returned by the server. + /// See GraphQL specification sections and + /// . member _.Errors = errors /// Gets all the custom data returned by the operation on server as a map of names and values. From 154929e57fdae82c520c9b88b6048a9ccae1e45e Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sun, 17 May 2026 13:06:53 +0000 Subject: [PATCH 10/14] Revert unintended introspection snapshot change Agent-Logs-Url: https://github.com/fsprojects/FSharp.Data.GraphQL/sessions/aabfb966-da13-41e9-821e-b097aa211948 Co-authored-by: xperiandri <2365592+xperiandri@users.noreply.github.com> --- tests/FSharp.Data.GraphQL.IntegrationTests/introspection.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/FSharp.Data.GraphQL.IntegrationTests/introspection.json b/tests/FSharp.Data.GraphQL.IntegrationTests/introspection.json index 9e468aa6..7b3d9abc 100644 --- a/tests/FSharp.Data.GraphQL.IntegrationTests/introspection.json +++ b/tests/FSharp.Data.GraphQL.IntegrationTests/introspection.json @@ -1,5 +1,5 @@ { - "documentId": -353990577, + "documentId": -128167532, "data": { "__schema": { "queryType": { From 28720754d86ac69f54c8dc3cd89ac0ed8e1980ef Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sun, 17 May 2026 13:28:09 +0000 Subject: [PATCH 11/14] Add exhaustive OperationError optional-field combination test Agent-Logs-Url: https://github.com/fsprojects/FSharp.Data.GraphQL/sessions/320a28b0-d97e-496d-a15c-3eded764faab Co-authored-by: xperiandri <2365592+xperiandri@users.noreply.github.com> --- .../OperationErrorTests.fs | 53 +++++++++++++++++++ 1 file changed, 53 insertions(+) diff --git a/tests/FSharp.Data.GraphQL.IntegrationTests/OperationErrorTests.fs b/tests/FSharp.Data.GraphQL.IntegrationTests/OperationErrorTests.fs index b2554448..d2545a76 100644 --- a/tests/FSharp.Data.GraphQL.IntegrationTests/OperationErrorTests.fs +++ b/tests/FSharp.Data.GraphQL.IntegrationTests/OperationErrorTests.fs @@ -47,6 +47,59 @@ let ``Should parse operation error fields from raw response`` () = error.Extensions.["retryable"] |> equals (box false) error.Extensions.["severity"] |> equals (box 7) +[] +let ``Should parse all combinations of optional operation error fields`` () = + let combinations = + [ for includePath in [ false; true ] do + for includeLocations in [ false; true ] do + for includeExtensions in [ false; true ] do + includePath, includeLocations, includeExtensions ] + + for includePath, includeLocations, includeExtensions in combinations do + let errorFields = ResizeArray([ "\"message\":\"unit-test combination error\"" ]) + + if includePath then + errorFields.Add "\"path\":[\"alwaysError\",0]" + + if includeLocations then + errorFields.Add "\"locations\":[{\"line\":2,\"column\":13}]" + + if includeExtensions then + errorFields.Add "\"extensions\":{\"code\":\"UNIT_TEST\",\"retryable\":false,\"severity\":7}" + + let responseJson = + $"""{{"errors":[{{{String.concat "," errorFields}}}]}}""" + + let result = + OperationResultBase( + rawResponse = new HttpResponseMessage(), + responseJson = JsonValue.Parse responseJson, + operationFields = [||], + operationTypeName = "Query" + ) + + result.Errors.Length |> equals 1 + + let error : FSharp.Data.GraphQL.OperationError = result.Errors.[0] + error.Message |> equals "unit-test combination error" + + if includePath then + error.Path |> equals [| box "alwaysError"; box 0 |] + else + error.Path |> equals [||] + + if includeLocations then + error.Locations |> equals [| { Line = 2; Column = 13 } |] + else + error.Locations |> equals [||] + + if includeExtensions then + error.Extensions.["code"] |> equals (box "UNIT_TEST") + error.Extensions.["retryable"] |> equals (box false) + error.Extensions.["severity"] |> equals (box 7) + else + error.Extensions |> equals Map.empty + [] let ``Should map server error extensions and locations into operation result`` () = let result = ErrorOperation.operation.Run() From 513aba4f32656474a24909f8fbf7b097766e15f3 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sun, 17 May 2026 13:32:25 +0000 Subject: [PATCH 12/14] Refine OperationError combination test JSON construction Agent-Logs-Url: https://github.com/fsprojects/FSharp.Data.GraphQL/sessions/320a28b0-d97e-496d-a15c-3eded764faab Co-authored-by: xperiandri <2365592+xperiandri@users.noreply.github.com> --- .../OperationErrorTests.fs | 26 +++++++++---------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/tests/FSharp.Data.GraphQL.IntegrationTests/OperationErrorTests.fs b/tests/FSharp.Data.GraphQL.IntegrationTests/OperationErrorTests.fs index d2545a76..5da4c37c 100644 --- a/tests/FSharp.Data.GraphQL.IntegrationTests/OperationErrorTests.fs +++ b/tests/FSharp.Data.GraphQL.IntegrationTests/OperationErrorTests.fs @@ -56,19 +56,19 @@ let ``Should parse all combinations of optional operation error fields`` () = includePath, includeLocations, includeExtensions ] for includePath, includeLocations, includeExtensions in combinations do - let errorFields = ResizeArray([ "\"message\":\"unit-test combination error\"" ]) - - if includePath then - errorFields.Add "\"path\":[\"alwaysError\",0]" - - if includeLocations then - errorFields.Add "\"locations\":[{\"line\":2,\"column\":13}]" - - if includeExtensions then - errorFields.Add "\"extensions\":{\"code\":\"UNIT_TEST\",\"retryable\":false,\"severity\":7}" - - let responseJson = - $"""{{"errors":[{{{String.concat "," errorFields}}}]}}""" + let optionalFields = + [ if includePath then + "\"path\":[\"alwaysError\",0]" + if includeLocations then + "\"locations\":[{\"line\":2,\"column\":13}]" + if includeExtensions then + "\"extensions\":{\"code\":\"UNIT_TEST\",\"retryable\":false,\"severity\":7}" ] + + let errorObjectJson = + "\"message\":\"unit-test combination error\"" :: optionalFields + |> String.concat "," + + let responseJson = $"""{{"errors":[{{{errorObjectJson}}}]}}""" let result = OperationResultBase( From e15c1cef7af4c134eafa57c144a798d401b3e391 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sun, 17 May 2026 13:46:19 +0000 Subject: [PATCH 13/14] Restore tools and run Fantomas twice on OperationErrorTests Agent-Logs-Url: https://github.com/fsprojects/FSharp.Data.GraphQL/sessions/d4e498c5-b77d-403f-bcfd-2ce7208c1e75 Co-authored-by: xperiandri <2365592+xperiandri@users.noreply.github.com> --- .../OperationErrorTests.fs | 49 ++++++++++--------- 1 file changed, 27 insertions(+), 22 deletions(-) diff --git a/tests/FSharp.Data.GraphQL.IntegrationTests/OperationErrorTests.fs b/tests/FSharp.Data.GraphQL.IntegrationTests/OperationErrorTests.fs index 5da4c37c..f95b44df 100644 --- a/tests/FSharp.Data.GraphQL.IntegrationTests/OperationErrorTests.fs +++ b/tests/FSharp.Data.GraphQL.IntegrationTests/OperationErrorTests.fs @@ -6,23 +6,24 @@ open Helpers open FSharp.Data.GraphQL open FSharp.Data.GraphQL.Client -let [] ServerUrl = "http://localhost:8085" +[] +let ServerUrl = "http://localhost:8085" -type Provider = GraphQLProvider +type Provider = GraphQLProvider module ErrorOperation = let operation = Provider.Operation<"""query ErrorQuery { alwaysError - }""">() + }"""> () type Operation = Provider.Operations.ErrorQuery [] let ``Should parse operation error fields from raw response`` () = let result = - OperationResultBase( - rawResponse = new HttpResponseMessage(), + OperationResultBase ( + rawResponse = new HttpResponseMessage (), responseJson = JsonValue.Parse """{ @@ -49,30 +50,33 @@ let ``Should parse operation error fields from raw response`` () = [] let ``Should parse all combinations of optional operation error fields`` () = - let combinations = - [ for includePath in [ false; true ] do - for includeLocations in [ false; true ] do - for includeExtensions in [ false; true ] do - includePath, includeLocations, includeExtensions ] + let combinations = [ + for includePath in [ false; true ] do + for includeLocations in [ false; true ] do + for includeExtensions in [ false; true ] do + includePath, includeLocations, includeExtensions + ] for includePath, includeLocations, includeExtensions in combinations do - let optionalFields = - [ if includePath then - "\"path\":[\"alwaysError\",0]" - if includeLocations then - "\"locations\":[{\"line\":2,\"column\":13}]" - if includeExtensions then - "\"extensions\":{\"code\":\"UNIT_TEST\",\"retryable\":false,\"severity\":7}" ] + let optionalFields = [ + if includePath then + "\"path\":[\"alwaysError\",0]" + if includeLocations then + "\"locations\":[{\"line\":2,\"column\":13}]" + if includeExtensions then + "\"extensions\":{\"code\":\"UNIT_TEST\",\"retryable\":false,\"severity\":7}" + ] let errorObjectJson = - "\"message\":\"unit-test combination error\"" :: optionalFields + "\"message\":\"unit-test combination error\"" + :: optionalFields |> String.concat "," let responseJson = $"""{{"errors":[{{{errorObjectJson}}}]}}""" let result = - OperationResultBase( - rawResponse = new HttpResponseMessage(), + OperationResultBase ( + rawResponse = new HttpResponseMessage (), responseJson = JsonValue.Parse responseJson, operationFields = [||], operationTypeName = "Query" @@ -102,7 +106,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 () result.Errors.Length |> equals 1 @@ -113,7 +117,8 @@ let ``Should map server error extensions and locations into operation result`` ( error.Locations |> equals [||] error.Extensions.ContainsKey "code" |> equals true - error.Extensions.["code"] |> equals (box "OPERATION_ERROR_TEST") + error.Extensions.["code"] + |> equals (box "OPERATION_ERROR_TEST") error.Extensions.ContainsKey "severity" |> equals true error.Extensions.["severity"] |> equals (box 7) error.Extensions.ContainsKey "kind" |> equals true From 861e2c13ba017e54c39533dd78fa4edcc23754df Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sun, 17 May 2026 14:00:59 +0000 Subject: [PATCH 14/14] Run Fantomas twice on all PR F# files Agent-Logs-Url: https://github.com/fsprojects/FSharp.Data.GraphQL/sessions/9a236d0a-8991-4f6e-852b-f7edbd7caef2 Co-authored-by: xperiandri <2365592+xperiandri@users.noreply.github.com> --- src/FSharp.Data.GraphQL.Client/BaseTypes.fs | 449 +++++++++++------- .../Schema.fs | 400 +++++++++------- 2 files changed, 490 insertions(+), 359 deletions(-) diff --git a/src/FSharp.Data.GraphQL.Client/BaseTypes.fs b/src/FSharp.Data.GraphQL.Client/BaseTypes.fs index d447c84c..4b93c3ff 100644 --- a/src/FSharp.Data.GraphQL.Client/BaseTypes.fs +++ b/src/FSharp.Data.GraphQL.Client/BaseTypes.fs @@ -15,13 +15,14 @@ open FSharp.Data.GraphQL.Client.ReflectionPatterns open FSharp.Data.GraphQL.Types.Introspection /// Contains information about a field on the query. -type SchemaFieldInfo = - { /// Gets the alias or the name of the field. - AliasOrName : string - /// Gets the introspection type information of the field. - SchemaTypeRef : IntrospectionTypeRef - /// Gets information about fields of this field, if it is an object type. - Fields : SchemaFieldInfo [] } +type SchemaFieldInfo = { + /// Gets the alias or the name of the field. + AliasOrName : string + /// Gets the introspection type information of the field. + SchemaTypeRef : IntrospectionTypeRef + /// Gets information about fields of this field, if it is an object type. + Fields : SchemaFieldInfo[] +} /// A type alias to represent a Type name. type TypeName = string @@ -29,24 +30,26 @@ type TypeName = string /// Contains source location information for a single GraphQL error location entry in the response. /// See GraphQL specification sections and /// . -type OperationErrorLocation = - { /// The source line of the GraphQL operation document where the error occurred. - Line : int - /// The source column of the GraphQL operation document where the error occurred. - Column : int } +type OperationErrorLocation = { + /// The source line of the GraphQL operation document where the error occurred. + Line : int + /// The source column of the GraphQL operation document where the error occurred. + Column : int +} /// Contains data about a GraphQL operation error as defined by the GraphQL response format. /// See GraphQL specification sections and /// . -type OperationError = - { /// The description of the error that happened in the operation. - Message : string - /// The source locations in the GraphQL operation document where the error occurred. - Locations : OperationErrorLocation [] - /// The path to the field that produced the error while resolving its value. - Path : obj [] - /// Extension data attached to the error. - Extensions : Map } +type OperationError = { + /// The description of the error that happened in the operation. + Message : string + /// The source locations in the GraphQL operation document where the error occurred. + Locations : OperationErrorLocation[] + /// The path to the field that produced the error while resolving its value. + Path : obj[] + /// Extension data attached to the error. + Extensions : Map +} /// Contains helpers to build HTTP header sequences to be used in GraphQLProvider Run methods. module HttpHeaders = @@ -54,138 +57,155 @@ module HttpHeaders = /// The input headers string should be a string containing headers in the same way they are /// organized in a HTTP request (each header in a line, names and values separated by commas). let ofString (headers : string) : seq = - upcast (headers.Replace("\r\n", "\n").Split('\n') - |> Array.map (fun header -> - let separatorIndex = header.IndexOf(':') - if separatorIndex = -1 - then failwithf "Header \"%s\" has an invalid header format. Must provide a name and a value, both separated by a comma." header - else - let name = header.Substring(0, separatorIndex).Trim() - let value = header.Substring(separatorIndex + 1).Trim() - (name, value))) + upcast + (headers.Replace("\r\n", "\n").Split ('\n') + |> Array.map (fun header -> + let separatorIndex = header.IndexOf (':') + if separatorIndex = -1 then + failwithf "Header \"%s\" has an invalid header format. Must provide a name and a value, both separated by a comma." header + else + let name = header.Substring(0, separatorIndex).Trim () + let value = header.Substring(separatorIndex + 1).Trim () + (name, value))) /// Builds a sequence of HTTP headers as a sequence from a header file. /// The input file should be a file containing headers in the same way they are /// organized in a HTTP request (each header in a line, names and values separated by commas). - let ofFile (path : string) = - System.IO.File.ReadAllText path |> ofString + let ofFile (path : string) = System.IO.File.ReadAllText path |> ofString let internal load (location : StringLocation) : seq = let headersString = match location with | String headers -> headers | File path -> System.IO.File.ReadAllText path - if headersString = "" then upcast [||] - else headersString |> ofString + if headersString = "" then + upcast [||] + else + headersString |> ofString /// The base type for all GraphQLProvider provided enum types. type EnumBase (name : string, value : string) = /// Gets the name of the provided enum type. - member _.GetName() = name + member _.GetName () = name /// Gets the value of the provided enum type. - member _.GetValue() = value + member _.GetValue () = value - override x.ToString() = x.GetValue() + override x.ToString () = x.GetValue () - member x.Equals(other : EnumBase) = - x.GetName() = other.GetName() && x.GetValue() = other.GetValue() + member x.Equals (other : EnumBase) = + x.GetName () = other.GetName () + && x.GetValue () = other.GetValue () - override x.Equals(other : obj) = + override x.Equals (other : obj) = match other with - | :? EnumBase as other -> x.Equals(other) + | :? EnumBase as other -> x.Equals (other) | _ -> false - override x.GetHashCode() = x.GetName().GetHashCode() ^^^ x.GetValue().GetHashCode() + override x.GetHashCode () = x.GetName().GetHashCode () ^^^ x.GetValue().GetHashCode () interface IEquatable with - member x.Equals(other) = x.Equals(other) + member x.Equals (other) = x.Equals (other) /// Contains information about a GraphQLProvider record property. -type RecordProperty = - { /// Gets the name of the record property. - Name : string - /// Gets the value of the record property. - Value : obj } +type RecordProperty = { + /// Gets the name of the record property. + Name : string + /// Gets the value of the record property. + Value : obj +} /// The base type for all GraphQLProvider provided record types. type RecordBase (name : string, properties : RecordProperty seq) = do - if not (isNull properties) - then - let distinctCount = properties |> Seq.map (fun p -> p.Name) |> Seq.distinct |> Seq.length - if distinctCount <> Seq.length properties - then failwith "Duplicated property names were found. Record can not be created, because each property name must be distinct." + if not (isNull properties) then + let distinctCount = + properties + |> Seq.map (fun p -> p.Name) + |> Seq.distinct + |> Seq.length + if distinctCount <> Seq.length properties then + failwith "Duplicated property names were found. Record can not be created, because each property name must be distinct." let properties = - if not (isNull properties) - then properties |> Seq.sortBy _.Name |> List.ofSeq - else [] + if not (isNull properties) then + properties |> Seq.sortBy _.Name |> List.ofSeq + else + [] /// Gets the name of this provided record type. - member _.GetName() = name + member _.GetName () = name /// Gets a list of this provided record properties. - member _.GetProperties() = properties + member _.GetProperties () = properties /// Produces a dictionary containing all the properties of this provided record type. - member x.ToDictionary() = + member x.ToDictionary () = let rec mapDictionaryValue (v : obj) = match v with | null -> null | :? string -> v // We need this because strings are enumerables, and we don't want to enumerate them recursively as an object - | :? EnumBase as v -> v.GetValue() |> box - | :? RecordBase as v -> box (v.ToDictionary()) + | :? EnumBase as v -> v.GetValue () |> box + | :? RecordBase as v -> box (v.ToDictionary ()) | OptionValue v -> v |> Option.map mapDictionaryValue |> Option.toObj | EnumerableValue v -> v |> Array.map mapDictionaryValue |> box | _ -> v - x.GetProperties() + x.GetProperties () |> Seq.choose (fun p -> - if not (isNull p.Value) - then Some (p.Name, mapDictionaryValue p.Value) - else None) + if not (isNull p.Value) then + Some (p.Name, mapDictionaryValue p.Value) + else + None) |> dict - override x.ToString() = + override x.ToString () = let getPropValue (prop : RecordProperty) = sprintf "%A" prop.Value - let sb = StringBuilder() - sb.Append("{") |> ignore + let sb = StringBuilder () + sb.Append ("{") |> ignore let rec printProperties (properties : RecordProperty list) = match properties with | [] -> () - | [prop] -> sb.Append(sprintf "%s = %s;" prop.Name (getPropValue prop)) |> ignore - | prop :: tail -> sb.AppendLine(sprintf "%s = %s;" prop.Name (getPropValue prop)) |> ignore; printProperties tail - printProperties (x.GetProperties()) - sb.Append("}") |> ignore - sb.ToString() - - member x.Equals(other : RecordBase) = - x.GetName() = other.GetName() && x.GetProperties() = other.GetProperties() - - override x.Equals(other : obj) = + | [ prop ] -> + sb.Append (sprintf "%s = %s;" prop.Name (getPropValue prop)) + |> ignore + | prop :: tail -> + sb.AppendLine (sprintf "%s = %s;" prop.Name (getPropValue prop)) + |> ignore + printProperties tail + printProperties (x.GetProperties ()) + sb.Append ("}") |> ignore + sb.ToString () + + member x.Equals (other : RecordBase) = + x.GetName () = other.GetName () + && x.GetProperties () = other.GetProperties () + + override x.Equals (other : obj) = match other with - | :? RecordBase as other -> x.Equals(other) + | :? RecordBase as other -> x.Equals (other) | _ -> false - override x.GetHashCode() = - x.GetName().GetHashCode() ^^^ x.GetProperties().GetHashCode() + override x.GetHashCode () = + x.GetName().GetHashCode () + ^^^ x.GetProperties().GetHashCode () interface IEquatable with - member x.Equals(other) = x.Equals(other) + member x.Equals (other) = x.Equals (other) module internal TypeMapping = let scalar = - [| "Int", typeof - "Boolean", typeof - "Date", typeof - "Float", typeof - "ID", typeof - "String", typeof - "URI", typeof |] + [| + "Int", typeof + "Boolean", typeof + "Date", typeof + "Float", typeof + "ID", typeof + "String", typeof + "URI", typeof + |] |> Map.ofArray - let isBuiltInScalarTypeName (name : string) = - scalar |> Map.containsKey name + let isBuiltInScalarTypeName (name : string) = scalar |> Map.containsKey name let isScalarTypeName (schemaTypes : Map) (name : string) = match schemaTypes.TryFind name with @@ -193,27 +213,32 @@ module internal TypeMapping = | None -> isBuiltInScalarTypeName name let tryFindScalarType (schemaTypes : Map) (name : string) = - if isScalarTypeName schemaTypes name - then scalar |> Map.tryFind name - else None + if isScalarTypeName schemaTypes name then + scalar |> Map.tryFind name + else + None let getSchemaTypes (introspection : IntrospectionSchema) = - let schemaTypeNames = - [| "__TypeKind" - "__DirectiveLocation" - "__Type" - "__InputValue" - "__Field" - "__EnumValue" - "__Directive" - "__Schema" |] - let isIntrospectionType (name : string) = - schemaTypeNames |> Array.contains name + let schemaTypeNames = [| + "__TypeKind" + "__DirectiveLocation" + "__Type" + "__InputValue" + "__Field" + "__EnumValue" + "__Directive" + "__Schema" + |] + let isIntrospectionType (name : string) = schemaTypeNames |> Array.contains name introspection.Types |> Array.choose (fun t -> - if not (isIntrospectionType t.Name) && not (t.Kind = TypeKind.SCALAR && isBuiltInScalarTypeName t.Name) - then Some(t.Name, t) - else None) + if + not (isIntrospectionType t.Name) + && not (t.Kind = TypeKind.SCALAR && isBuiltInScalarTypeName t.Name) + then + Some (t.Name, t) + else + None) |> Map.ofArray let mapScalarType uploadInputTypeName tname = @@ -221,20 +246,25 @@ module internal TypeMapping = | Some uploadInputTypeName when uploadInputTypeName = tname -> typeof | _ -> // Unknown scalar types will be mapped to a string type. - if scalar.ContainsKey(tname) - then scalar.[tname] - else typeof + if scalar.ContainsKey (tname) then + scalar.[tname] + else + typeof - let makeOption (t : Type) = typedefof<_ option>.MakeGenericType(t) + let makeOption (t : Type) = typedefof<_ option>.MakeGenericType (t) - let makeArray (t : Type) = t.MakeArrayType() + let makeArray (t : Type) = t.MakeArrayType () let unwrapOption (t : Type) = - if t.IsGenericType && t.GetGenericTypeDefinition() = typedefof<_ option> - then t.GetGenericArguments().[0] - else failwithf "Expected type to be an Option type, but it is %s." t.Name + if + t.IsGenericType + && t.GetGenericTypeDefinition () = typedefof<_ option> + then + t.GetGenericArguments().[0] + else + failwithf "Expected type to be an Option type, but it is %s." t.Name - let makeAsync (t : Type) = typedefof>.MakeGenericType(t) + let makeAsync (t : Type) = typedefof>.MakeGenericType (t) module internal JsonValueHelper = let getResponseFields (responseJson : JsonValue) = @@ -243,7 +273,10 @@ module internal JsonValueHelper = | _ -> failwithf "Expected root type to be a Record type, but type is %A." responseJson let getResponseDataFields (responseJson : JsonValue) = - match getResponseFields responseJson |> Array.tryFind (fun (name, _) -> name = "data") with + match + getResponseFields responseJson + |> Array.tryFind (fun (name, _) -> name = "data") + with | Some (_, data) -> match data with | JsonValue.Record fields -> Some fields @@ -252,10 +285,14 @@ module internal JsonValueHelper = | None -> None let getResponseErrors (responseJson : JsonValue) = - match getResponseFields responseJson |> Array.tryFind (fun (name, _) -> name = "errors") with + match + getResponseFields responseJson + |> Array.tryFind (fun (name, _) -> name = "errors") + with | Some (_, errors) -> match errors with - | JsonValue.Array [||] | JsonValue.Null -> None + | JsonValue.Array [||] + | JsonValue.Null -> None | JsonValue.Array items -> Some items | _ -> failwithf "Expected error field of root type to be an Array type, but type is %A." errors | None -> None @@ -264,11 +301,11 @@ module internal JsonValueHelper = getResponseFields responseJson |> Array.filter (fun (name, _) -> name <> "data" && name <> "errors") - let private removeTypeNameField (fields : (string * JsonValue) []) = - fields |> Array.filter (fun (name, _) -> name <> "__typename") + let private removeTypeNameField (fields : (string * JsonValue)[]) = + fields + |> Array.filter (fun (name, _) -> name <> "__typename") - let firstUpper (name : string, value) = - name.FirstCharUpper(), value + let firstUpper (name : string, value) = name.FirstCharUpper (), value let getTypeName (fields : (string * JsonValue) seq) = fields @@ -308,7 +345,10 @@ module internal JsonValueHelper = | TypeKind.NON_NULL -> match schemaField.SchemaTypeRef.OfType with | Some t when t.Kind = TypeKind.LIST -> t.OfType - | _ -> failwithf "Expected field to be a list type with an underlying item, but it is %A." schemaField.SchemaTypeRef.OfType + | _ -> + failwithf + "Expected field to be a list type with an underlying item, but it is %A." + schemaField.SchemaTypeRef.OfType | _ -> failwithf "Expected field to be a list type with an underlying item, but it is %A." schemaField.SchemaTypeRef match tref with | Some t -> t @@ -322,12 +362,16 @@ module internal JsonValueHelper = | Some itemType -> match itemType.Kind with | TypeKind.NON_NULL -> failwith "Schema definition is not supported: a non null type of a non null type was specified." - | TypeKind.OBJECT | TypeKind.INTERFACE | TypeKind.UNION -> makeArray typeof items + | TypeKind.OBJECT + | TypeKind.INTERFACE + | TypeKind.UNION -> makeArray typeof items | TypeKind.ENUM -> makeArray typeof items | TypeKind.SCALAR -> makeArray (getScalarType itemType) items | kind -> failwithf "Unsupported type kind \"%A\"." kind | None -> failwith "Item type is a non null type, but no underlying type exists on the schema definition of the type." - | TypeKind.OBJECT | TypeKind.INTERFACE | TypeKind.UNION -> makeOptionArray typeof items + | TypeKind.OBJECT + | TypeKind.INTERFACE + | TypeKind.UNION -> makeOptionArray typeof items | TypeKind.ENUM -> makeOptionArray typeof items | TypeKind.SCALAR -> makeOptionArray (getScalarType itemType) items | kind -> failwithf "Unsupported type kind \"%A\"." kind @@ -339,22 +383,31 @@ module internal JsonValueHelper = | None -> failwith "Expected type to have a \"__typename\" field, but it was not found." let mapRecordProperty (aliasOrName : string, value : JsonValue) = let schemaField = - match schemaField.Fields |> Array.tryFind (fun f -> f.AliasOrName = aliasOrName) with + match + schemaField.Fields + |> Array.tryFind (fun f -> f.AliasOrName = aliasOrName) + with | Some f -> f - | None -> failwithf "Expected to find field information for field with alias or name \"%s\" of type \"%s\" but it was not found." aliasOrName typeName + | None -> + failwithf + "Expected to find field information for field with alias or name \"%s\" of type \"%s\" but it was not found." + aliasOrName + typeName let value = helper true schemaField value { Name = aliasOrName; Value = value } let props = props |> removeTypeNameField |> Array.map (firstUpper >> mapRecordProperty) - RecordBase(typeName, props) |> makeSomeIfNeeded + RecordBase (typeName, props) |> makeSomeIfNeeded | JsonValue.Boolean b -> makeSomeIfNeeded b | JsonValue.Float f -> makeSomeIfNeeded f | JsonValue.Null -> match schemaField.SchemaTypeRef.Kind with | TypeKind.NON_NULL -> failwith "Expected a non null item from the schema definition, but a null item was found in the response." - | TypeKind.OBJECT | TypeKind.INTERFACE | TypeKind.UNION -> makeNoneIfNeeded typeof + | TypeKind.OBJECT + | TypeKind.INTERFACE + | TypeKind.UNION -> makeNoneIfNeeded typeof | TypeKind.ENUM -> makeNoneIfNeeded typeof | TypeKind.SCALAR -> getScalarType schemaField.SchemaTypeRef |> makeNoneIfNeeded | TypeKind.LIST -> null @@ -369,98 +422,129 @@ module internal JsonValueHelper = | TypeKind.NON_NULL -> failwith "Schema definition is not supported: a non null type of a non null type was specified." | TypeKind.SCALAR -> match itemType.Name with - | Some "URI" -> - System.Uri(s) |> box + | Some "URI" -> System.Uri (s) |> box | Some "Date" -> - match DateTime.TryParse(s, CultureInfo.InvariantCulture, DateTimeStyles.None) with + match DateTime.TryParse (s, CultureInfo.InvariantCulture, DateTimeStyles.None) with | (true, d) -> box d - | _ -> failwith "A string was received in the query response, and the schema recognizes it as a date and time string, but the conversion failed." - | Some _ -> - box s - | _ -> failwith "A string type was received in the query response item, but the matching schema field is not a string based type." - | TypeKind.ENUM when itemType.Name.IsSome -> EnumBase(itemType.Name.Value, s) |> box - | _ -> failwith "A string type was received in the query response item, but the matching schema field is not a string or an enum type." + | _ -> + failwith + "A string was received in the query response, and the schema recognizes it as a date and time string, but the conversion failed." + | Some _ -> box s + | _ -> + failwith + "A string type was received in the query response item, but the matching schema field is not a string based type." + | TypeKind.ENUM when itemType.Name.IsSome -> EnumBase (itemType.Name.Value, s) |> box + | _ -> + failwith + "A string type was received in the query response item, but the matching schema field is not a string or an enum type." | None -> failwith "Item type is a non null type, but no underlying type exists on the schema definition of the type." | TypeKind.SCALAR -> match schemaField.SchemaTypeRef.Name with - | Some "String" | Some "ID" -> - s |> makeSomeIfNeeded - | Some "URI" -> - s |> System.Uri |> makeSomeIfNeeded + | Some "String" + | Some "ID" -> s |> makeSomeIfNeeded + | Some "URI" -> s |> System.Uri |> makeSomeIfNeeded | Some "Date" -> - match DateTime.TryParse(s, CultureInfo.InvariantCulture, DateTimeStyles.None) with + match DateTime.TryParse (s, CultureInfo.InvariantCulture, DateTimeStyles.None) with | (true, d) -> makeSomeIfNeeded d - | _ -> failwith "A string was received in the query response, and the schema recognizes it as a date and time string, but the conversion failed." - | Some _ -> - s |> makeSomeIfNeeded + | _ -> + failwith + "A string was received in the query response, and the schema recognizes it as a date and time string, but the conversion failed." + | Some _ -> s |> makeSomeIfNeeded | _ -> failwith "A string type was received in the query response item, but the matching schema field is not a string based type." - | TypeKind.ENUM when schemaField.SchemaTypeRef.Name.IsSome -> EnumBase(schemaField.SchemaTypeRef.Name.Value, s) |> makeSomeIfNeeded - | _ -> failwith "A string type was received in the query response item, but the matching schema field is not a string based type or an enum type." + | TypeKind.ENUM when schemaField.SchemaTypeRef.Name.IsSome -> + EnumBase (schemaField.SchemaTypeRef.Name.Value, s) + |> makeSomeIfNeeded + | _ -> + failwith + "A string type was received in the query response item, but the matching schema field is not a string based type or an enum type." fieldName, (helper true schemaField fieldValue) - let getFieldValues (schemaTypeName : string) (schemaFields : SchemaFieldInfo []) (dataFields : (string * JsonValue) []) = + let getFieldValues (schemaTypeName : string) (schemaFields : SchemaFieldInfo[]) (dataFields : (string * JsonValue)[]) = let mapFieldValue (aliasOrName : string, value : JsonValue) = let schemaField = - match schemaFields |> Array.tryFind (fun f -> f.AliasOrName = aliasOrName) with + match + schemaFields + |> Array.tryFind (fun f -> f.AliasOrName = aliasOrName) + with | Some f -> f - | None -> failwithf "Expected to find field information for field with alias or name \"%s\" of type \"%s\" but it was not found." aliasOrName schemaTypeName + | None -> + failwithf + "Expected to find field information for field with alias or name \"%s\" of type \"%s\" but it was not found." + aliasOrName + schemaTypeName getFieldValue schemaField (aliasOrName, value) removeTypeNameField dataFields |> Array.map (firstUpper >> mapFieldValue) - let getErrors (errors : JsonValue []) = - let tryFindField fieldName (fields : (string * JsonValue) []) = - fields |> Array.tryFind (fun (name, _) -> name = fieldName) |> Option.map snd + let getErrors (errors : JsonValue[]) = + let tryFindField fieldName (fields : (string * JsonValue)[]) = + fields + |> Array.tryFind (fun (name, _) -> name = fieldName) + |> Option.map snd - let parsePath = function + let parsePath = + function | Some (JsonValue.Array path) -> - let pathMapper = function + let pathMapper = + function | JsonValue.String x -> box x | JsonValue.Integer x -> box x | _ -> failwith "Error parsing response errors. An item in the path is neither a String nor an Integer." path |> Array.map pathMapper - | Some JsonValue.Null | None -> [||] + | Some JsonValue.Null + | None -> [||] | _ -> failwith "Error parsing response errors. Path field must be an Array." - let parseLocations = function + let parseLocations = + function | Some (JsonValue.Array locations) -> - let parseLocation = function + let parseLocation = + function | JsonValue.Record locationFields -> match tryFindField "line" locationFields, tryFindField "column" locationFields with | Some (JsonValue.Integer line), Some (JsonValue.Integer column) -> { Line = line; Column = column } | _ -> failwith "Error parsing response errors. A location item must contain Integer fields named \"line\" and \"column\"." | _ -> failwith "Error parsing response errors. A location item is not a Record." locations |> Array.map parseLocation - | Some JsonValue.Null | None -> [||] + | Some JsonValue.Null + | None -> [||] | _ -> failwith "Error parsing response errors. Locations field must be an Array." - let parseExtensions = function + let parseExtensions = + function | Some (JsonValue.Record fields) -> Serialization.deserializeMap fields - | Some JsonValue.Null | None -> Map.empty + | Some JsonValue.Null + | None -> Map.empty | _ -> failwith "Error parsing response errors. Extensions field must be a Record." - let errorMapper = function + let errorMapper = + function | JsonValue.Record fields -> match tryFindField "message" fields with - | Some (JsonValue.String message) -> - { Message = message - Locations = tryFindField "locations" fields |> parseLocations - Path = tryFindField "path" fields |> parsePath - Extensions = tryFindField "extensions" fields |> parseExtensions } + | Some (JsonValue.String message) -> { + Message = message + Locations = tryFindField "locations" fields |> parseLocations + Path = tryFindField "path" fields |> parsePath + Extensions = tryFindField "extensions" fields |> parseExtensions + } | _ -> failwith "Error parsing response errors. Unsupported errors field format." - | other -> failwithf "Error parsing response errors. Expected error to be a Record type, but it is %s." (other.ToString()) + | other -> failwithf "Error parsing response errors. Expected error to be a Record type, but it is %s." (other.ToString ()) Array.map errorMapper errors /// The base type for all GraphQLProvider operation result provided types. -type OperationResultBase (rawResponse: HttpResponseMessage, responseJson : JsonValue, operationFields : SchemaFieldInfo [], operationTypeName : string) = +type OperationResultBase + (rawResponse : HttpResponseMessage, responseJson : JsonValue, operationFields : SchemaFieldInfo[], operationTypeName : string) = let rawData = let data = JsonValueHelper.getResponseDataFields responseJson match data with - | Some [||] | None -> None + | Some [||] + | None -> None | Some dataFields -> let fieldValues = JsonValueHelper.getFieldValues operationTypeName operationFields dataFields - let props = fieldValues |> Array.map (fun (name, value) -> { Name = name; Value = value }) - Some (RecordBase(operationTypeName, props)) + let props = + fieldValues + |> Array.map (fun (name, value) -> { Name = name; Value = value }) + Some (RecordBase (operationTypeName, props)) let errors = let errors = JsonValueHelper.getResponseErrors responseJson @@ -476,7 +560,7 @@ type OperationResultBase (rawResponse: HttpResponseMessage, responseJson : JsonV /// [] - [] + [] member _.RawData = rawData /// Gets all GraphQL errors returned by the server. @@ -489,15 +573,14 @@ type OperationResultBase (rawResponse: HttpResponseMessage, responseJson : JsonV member _.Headers = rawResponse.Headers - member x.Equals(other : OperationResultBase) = - x.ResponseJson = other.ResponseJson + member x.Equals (other : OperationResultBase) = x.ResponseJson = other.ResponseJson - override x.Equals(other : obj) = + override x.Equals (other : obj) = match other with - | :? OperationResultBase as other -> x.Equals(other) + | :? OperationResultBase as other -> x.Equals (other) | _ -> false - override x.GetHashCode() = x.ResponseJson.GetHashCode() + override x.GetHashCode () = x.ResponseJson.GetHashCode () /// The base type for al GraphQLProvider operation provided types. type OperationBase (query : string) = @@ -512,8 +595,8 @@ module VariableMapping = match value with | null -> null | :? string -> value - | :? EnumBase as v -> v.GetValue() |> box - | :? RecordBase as v -> v.ToDictionary() |> box + | :? EnumBase as v -> v.GetValue () |> box + | :? RecordBase as v -> v.ToDictionary () |> box | OptionValue v -> v |> Option.map mapVariableValue |> box | EnumerableValue v -> v |> Array.map mapVariableValue |> box | v -> v diff --git a/tests/FSharp.Data.GraphQL.IntegrationTests.Server/Schema.fs b/tests/FSharp.Data.GraphQL.IntegrationTests.Server/Schema.fs index 5c7fc87f..5ba9de7d 100644 --- a/tests/FSharp.Data.GraphQL.IntegrationTests.Server/Schema.fs +++ b/tests/FSharp.Data.GraphQL.IntegrationTests.Server/Schema.fs @@ -6,225 +6,273 @@ open System.Collections.Generic open Microsoft.AspNetCore.Http open Microsoft.Extensions.DependencyInjection -type Root(ctx : HttpContext) = +type Root (ctx : HttpContext) = - member _.RequestAborted: System.Threading.CancellationToken = ctx.RequestAborted - member _.ServiceProvider: IServiceProvider = ctx.RequestServices - member root.GetRequiredService<'t>() = root.ServiceProvider.GetRequiredService<'t>() + member _.RequestAborted : System.Threading.CancellationToken = ctx.RequestAborted + member _.ServiceProvider : IServiceProvider = ctx.RequestServices + member root.GetRequiredService<'t> () = root.ServiceProvider.GetRequiredService<'t> () open FSharp.Data.GraphQL open FSharp.Data.GraphQL.Types -type InputField = - { String : string - Int : int - StringOption : string option - IntOption : int option - Uri : Uri - Guid : Guid - GuidOption : Guid option } - -type Input = - { Single : InputField option - List : InputField list option } - -type InputFile = - { File : FileData } - -type UploadedFile = - { Name : string - ContentType : string - ContentAsText : string } - -type UploadedContentFile = - { Name : string - ContentAsText : string } - -type UploadRequest = - { Single : FileData - Multiple : FileData list - NullableMultiple : FileData list option - NullableMultipleNullable : FileData option list option } - -type UploadResponse = - { Single : UploadedFile - Multiple : UploadedFile list - NullableMultiple : UploadedFile list option - NullableMultipleNullable : UploadedFile option list option } +type InputField = { + String : string + Int : int + StringOption : string option + IntOption : int option + Uri : Uri + Guid : Guid + GuidOption : Guid option +} + +type Input = { Single : InputField option; List : InputField list option } + +type InputFile = { File : FileData } + +type UploadedFile = { Name : string; ContentType : string; ContentAsText : string } + +type UploadedContentFile = { Name : string; ContentAsText : string } + +type UploadRequest = { + Single : FileData + Multiple : FileData list + NullableMultiple : FileData list option + NullableMultipleNullable : FileData option list option +} + +type UploadResponse = { + Single : UploadedFile + Multiple : UploadedFile list + NullableMultiple : UploadedFile list option + NullableMultipleNullable : UploadedFile option list option +} module Schema = let InputFieldType = - Define.InputObject( + Define.InputObject ( name = "InputField", - fields = - [ Define.Input("string", StringType, description = "A string value.") - Define.Input("int", IntType, description = "An integer value.") - Define.Input("stringOption", Nullable StringType, description = "A string option value.") - Define.Input("intOption", Nullable IntType, description = "An integer option value.") - Define.Input("uri", UriType, description = "An URI value.") - Define.Input("guid", GuidType, description = "A Guid value.") ]) + fields = [ + Define.Input ("string", StringType, description = "A string value.") + Define.Input ("int", IntType, description = "An integer value.") + Define.Input ("stringOption", Nullable StringType, description = "A string option value.") + Define.Input ("intOption", Nullable IntType, description = "An integer option value.") + Define.Input ("uri", UriType, description = "An URI value.") + Define.Input ("guid", GuidType, description = "A Guid value.") + ] + ) let InputType = - Define.InputObject( - name ="Input", + Define.InputObject ( + name = "Input", description = "Input object type.", - fields = - [ Define.Input("single", Nullable InputFieldType, description = "A single input field.") - Define.Input("list", Nullable (ListOf InputFieldType), description = "A list of input fields.") ]) + fields = [ + Define.Input ("single", Nullable InputFieldType, description = "A single input field.") + Define.Input ("list", Nullable (ListOf InputFieldType), description = "A list of input fields.") + ] + ) let OutputFieldType = - Define.Object( + Define.Object ( name = "OutputField", description = "The output for a field input.", - fields = - [ Define.Field("string", StringType, resolve = (fun _ x -> x.String), description = "A string value.") - Define.AutoField("int", IntType, description = "An integer value.") - Define.AutoField("stringOption", Nullable StringType, description = "A string option value.") - Define.AutoField("intOption", Nullable IntType, description = "An integer option value.") - Define.AutoField("uri", UriType, description = "An URI value.") - Define.AutoField("guid", GuidType, description = "A Guid value.") - Define.Field("guidId", IDType, description = "A Guid Id value.", resolve = fun _ o -> o.Guid |> string) - Define.Field("stringId", IDType, description = "A String Id value.", resolve = fun _ o -> o.String) - Define.Field("guidIdOption", Nullable IDType, description = "A Guid Id value.", resolve = fun _ o -> o.GuidOption |> Option.map string) - Define.Field("stringIdOption", Nullable IDType, description = "A String Id value.", resolve = fun _ o -> o.StringOption) - Define.Field("deprecated", StringType, resolve = (fun _ x -> x.String), description = "A string value through a deprecated field.", deprecationReason = "This field is deprecated.", args = []) ]) + fields = [ + Define.Field ("string", StringType, resolve = (fun _ x -> x.String), description = "A string value.") + Define.AutoField ("int", IntType, description = "An integer value.") + Define.AutoField ("stringOption", Nullable StringType, description = "A string option value.") + Define.AutoField ("intOption", Nullable IntType, description = "An integer option value.") + Define.AutoField ("uri", UriType, description = "An URI value.") + Define.AutoField ("guid", GuidType, description = "A Guid value.") + Define.Field ("guidId", IDType, description = "A Guid Id value.", resolve = (fun _ o -> o.Guid |> string)) + Define.Field ("stringId", IDType, description = "A String Id value.", resolve = (fun _ o -> o.String)) + Define.Field ( + "guidIdOption", + Nullable IDType, + description = "A Guid Id value.", + resolve = fun _ o -> o.GuidOption |> Option.map string + ) + Define.Field ("stringIdOption", Nullable IDType, description = "A String Id value.", resolve = (fun _ o -> o.StringOption)) + Define.Field ( + "deprecated", + StringType, + resolve = (fun _ x -> x.String), + description = "A string value through a deprecated field.", + deprecationReason = "This field is deprecated.", + args = [] + ) + ] + ) let UploadedFileType = - Define.Object( + Define.Object ( name = "UploadedFile", description = "Contains data of an uploaded file.", - fields = - [ Define.AutoField("name", StringType, description = "The name of the file.") - Define.AutoField("contentType", StringType, description = "The content type of the file.") - Define.AutoField("contentAsText", StringType, description = "The content of the file as text.") ]) + fields = [ + Define.AutoField ("name", StringType, description = "The name of the file.") + Define.AutoField ("contentType", StringType, description = "The content type of the file.") + Define.AutoField ("contentAsText", StringType, description = "The content of the file as text.") + ] + ) let UploadRequestType = - Define.InputObject( + Define.InputObject ( name = "UploadRequest", description = "Request for uploading files in several different forms.", - fields = - [ Define.Input("single", FileType, description = "A single file upload.") - Define.Input("multiple", ListOf FileType, description = "Multiple file uploads.") - Define.Input("nullableMultiple", Nullable (ListOf FileType), description = "Optional list of multiple file uploads.") - Define.Input("nullableMultipleNullable", Nullable (ListOf (Nullable FileType)), description = "Optional list of multiple optional file uploads.") ]) + fields = [ + Define.Input ("single", FileType, description = "A single file upload.") + Define.Input ("multiple", ListOf FileType, description = "Multiple file uploads.") + Define.Input ("nullableMultiple", Nullable (ListOf FileType), description = "Optional list of multiple file uploads.") + Define.Input ( + "nullableMultipleNullable", + Nullable (ListOf (Nullable FileType)), + description = "Optional list of multiple optional file uploads." + ) + ] + ) let UploadResponseType = - Define.Object( + Define.Object ( name = "UploadResponse", description = "Contains uploaded files of an upload files request.", - fields = - [ Define.AutoField("single", UploadedFileType, description = "A single file upload.") - Define.AutoField("multiple", ListOf UploadedFileType, description = "Multiple file uploads.") - Define.AutoField("nullableMultiple", Nullable (ListOf UploadedFileType), description = "Optional list of multiple file uploads.") - Define.AutoField("nullableMultipleNullable", Nullable (ListOf (Nullable UploadedFileType)), description = "Optional list of multiple optional file uploads.") ]) + fields = [ + Define.AutoField ("single", UploadedFileType, description = "A single file upload.") + Define.AutoField ("multiple", ListOf UploadedFileType, description = "Multiple file uploads.") + Define.AutoField ("nullableMultiple", Nullable (ListOf UploadedFileType), description = "Optional list of multiple file uploads.") + Define.AutoField ( + "nullableMultipleNullable", + Nullable (ListOf (Nullable UploadedFileType)), + description = "Optional list of multiple optional file uploads." + ) + ] + ) let OutputType = - Define.Object( + Define.Object ( name = "Output", description = "The output for an input.", - fields = - [ Define.AutoField("single", Nullable OutputFieldType, description = "A single output field.") - Define.AutoField("list", Nullable (ListOf OutputFieldType), description = "A list of output fields.") ]) + fields = [ + Define.AutoField ("single", Nullable OutputFieldType, description = "A single output field.") + Define.AutoField ("list", Nullable (ListOf OutputFieldType), description = "A list of output fields.") + ] + ) let QueryType = - Define.Object( + Define.Object ( name = "Query", description = "The query type.", - fields = - [ Define.Field( + fields = [ + Define.Field ( name = "echo", typedef = StructNullable OutputType, description = "Enters an input type and get it back.", - args = [ Define.Input("input", Nullable InputType, description = "The input to be echoed as an output.") ], - resolve = fun ctx _ -> ctx.TryArg("input")) - Define.Field( - name = "alwaysError", - typedef = Nullable StringType, - description = "Always produces an execution error for integration tests.", - args = [], - resolve = - fun _ _ -> - let extensions = - Dictionary( - [ KeyValuePair("code", box "OPERATION_ERROR_TEST") - KeyValuePair("severity", box 7) ] - ) - raise (GQLMessageException("Always fails for tests", extensions)) - ) ]) - - let InputFileObject = Define.InputObject( - name = "InputFile", - fields = - [ - Define.Input("file", FileType) - ]) + args = [ Define.Input ("input", Nullable InputType, description = "The input to be echoed as an output.") ], + resolve = fun ctx _ -> ctx.TryArg ("input") + ) + Define.Field ( + name = "alwaysError", + typedef = Nullable StringType, + description = "Always produces an execution error for integration tests.", + args = [], + resolve = + fun _ _ -> + let extensions = + Dictionary ([ KeyValuePair ("code", box "OPERATION_ERROR_TEST"); KeyValuePair ("severity", box 7) ]) + raise (GQLMessageException ("Always fails for tests", extensions)) + ) + ] + ) + + let InputFileObject = + Define.InputObject (name = "InputFile", fields = [ Define.Input ("file", FileType) ]) let MutationType = let contentAsText (stream : System.IO.Stream) = - use reader = new System.IO.StreamReader(stream, Encoding.UTF8) - reader.ReadToEnd() + use reader = new System.IO.StreamReader (stream, Encoding.UTF8) + reader.ReadToEnd () let getFileContent (ctx : ResolveFieldContext) argName = let inputFile = ctx.Arg argName let stream = inputFile.File.Stream - use reader = new System.IO.StreamReader(stream, Encoding.UTF8, true) - reader.ReadToEnd() - let mapUploadToOutput (file : FileData) = - { Name = file.FileName; ContentType = file.ContentType; ContentAsText = contentAsText file.Stream } - let mapUploadRequestToOutput (request : UploadRequest) = - { - Single = mapUploadToOutput request.Single - Multiple = request.Multiple |> List.map mapUploadToOutput - NullableMultiple = request.NullableMultiple |> Option.map (List.map mapUploadToOutput) - NullableMultipleNullable = request.NullableMultipleNullable |> Option.map (List.map (Option.map mapUploadToOutput)) - } - Define.Object( + use reader = new System.IO.StreamReader (stream, Encoding.UTF8, true) + reader.ReadToEnd () + let mapUploadToOutput (file : FileData) = { + Name = file.FileName + ContentType = file.ContentType + ContentAsText = contentAsText file.Stream + } + let mapUploadRequestToOutput (request : UploadRequest) = { + Single = mapUploadToOutput request.Single + Multiple = request.Multiple |> List.map mapUploadToOutput + NullableMultiple = + request.NullableMultiple + |> Option.map (List.map mapUploadToOutput) + NullableMultipleNullable = + request.NullableMultipleNullable + |> Option.map (List.map (Option.map mapUploadToOutput)) + } + Define.Object ( name = "Mutation", - fields = - [ - Define.Field( - name = "singleUpload", - typedef = UploadedFileType, - description = "Uploads a single file to the server and get it back.", - args = [ Define.Input("file", FileType, description = "The file to be uploaded.") ], - resolve = fun ctx _ -> mapUploadToOutput (ctx.Arg("file"))) - Define.Field( - name = "nullableSingleUpload", - typedef = StructNullable UploadedFileType, - description = "Uploads (maybe) a single file to the server and get it back (maybe).", - args = [ Define.Input("file", Nullable FileType, description = "The file to be uploaded.") ], - resolve = fun ctx _ -> ctx.TryArg("file") |> ValueOption.map mapUploadToOutput) - Define.Field( - name = "multipleUpload", - typedef = ListOf UploadedFileType, - description = "Uploads a list of files to the server and get them back.", - args = [ Define.Input("files", ListOf FileType, description = "The files to upload.") ], - resolve = fun ctx _ -> ctx.Arg("files") |> List.map mapUploadToOutput) - Define.Field( - name = "nullableMultipleUpload", - typedef = StructNullable (ListOf UploadedFileType), - description = "Uploads (maybe) a list of files to the server and get them back (maybe).", - args = [ Define.Input("files", Nullable (ListOf FileType), description = "The files to upload.") ], - resolve = fun ctx _ -> ctx.TryArg("files") |> ValueOption.map (List.map mapUploadToOutput)) - Define.Field( - name = "nullableMultipleNullableUpload", - typedef = StructNullable (ListOf (Nullable UploadedFileType)), - description = "Uploads (maybe) a list of files (maybe) to the server and get them back (maybe).", - args = [ Define.Input("files", Nullable (ListOf (Nullable FileType)), description = "The files to upload.") ], - resolve = fun ctx _ -> ctx.TryArg("files") |> ValueOption.map (List.map (Option.map mapUploadToOutput))) - Define.Field( - name = "uploadRequest", - typedef = UploadResponseType, - description = "Upload several files in different forms.", - args = [ Define.Input("request", UploadRequestType, description = "The request for uploading several files in different forms.") ], - resolve = fun ctx _ -> mapUploadRequestToOutput (ctx.Arg("request"))) - Define.Field ( - name = "uploadComplex", - typedef = StringType, - description = "", - args = [ Define.Input ("input", InputFileObject) ], - resolve = fun ctx _ -> getFileContent ctx "input") - ]) - - let schema : ISchema = upcast Schema(QueryType, MutationType) - - let executor = Executor(schema) + fields = [ + Define.Field ( + name = "singleUpload", + typedef = UploadedFileType, + description = "Uploads a single file to the server and get it back.", + args = [ Define.Input ("file", FileType, description = "The file to be uploaded.") ], + resolve = fun ctx _ -> mapUploadToOutput (ctx.Arg ("file")) + ) + Define.Field ( + name = "nullableSingleUpload", + typedef = StructNullable UploadedFileType, + description = "Uploads (maybe) a single file to the server and get it back (maybe).", + args = [ Define.Input ("file", Nullable FileType, description = "The file to be uploaded.") ], + resolve = fun ctx _ -> ctx.TryArg ("file") |> ValueOption.map mapUploadToOutput + ) + Define.Field ( + name = "multipleUpload", + typedef = ListOf UploadedFileType, + description = "Uploads a list of files to the server and get them back.", + args = [ Define.Input ("files", ListOf FileType, description = "The files to upload.") ], + resolve = fun ctx _ -> ctx.Arg ("files") |> List.map mapUploadToOutput + ) + Define.Field ( + name = "nullableMultipleUpload", + typedef = StructNullable (ListOf UploadedFileType), + description = "Uploads (maybe) a list of files to the server and get them back (maybe).", + args = [ Define.Input ("files", Nullable (ListOf FileType), description = "The files to upload.") ], + resolve = + fun ctx _ -> + ctx.TryArg ("files") + |> ValueOption.map (List.map mapUploadToOutput) + ) + Define.Field ( + name = "nullableMultipleNullableUpload", + typedef = StructNullable (ListOf (Nullable UploadedFileType)), + description = "Uploads (maybe) a list of files (maybe) to the server and get them back (maybe).", + args = [ + Define.Input ("files", Nullable (ListOf (Nullable FileType)), description = "The files to upload.") + ], + resolve = + fun ctx _ -> + ctx.TryArg ("files") + |> ValueOption.map (List.map (Option.map mapUploadToOutput)) + ) + Define.Field ( + name = "uploadRequest", + typedef = UploadResponseType, + description = "Upload several files in different forms.", + args = [ + Define.Input ("request", UploadRequestType, description = "The request for uploading several files in different forms.") + ], + resolve = fun ctx _ -> mapUploadRequestToOutput (ctx.Arg ("request")) + ) + Define.Field ( + name = "uploadComplex", + typedef = StringType, + description = "", + args = [ Define.Input ("input", InputFileObject) ], + resolve = fun ctx _ -> getFileContent ctx "input" + ) + ] + ) + + let schema : ISchema = upcast Schema (QueryType, MutationType) + + let executor = Executor (schema)