diff --git a/src/FSharp.Data.GraphQL.Client/BaseTypes.fs b/src/FSharp.Data.GraphQL.Client/BaseTypes.fs
index 5bc66444..4b93c3ff 100644
--- a/src/FSharp.Data.GraphQL.Client/BaseTypes.fs
+++ b/src/FSharp.Data.GraphQL.Client/BaseTypes.fs
@@ -15,23 +15,41 @@ 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
-/// Contains data about a GQL operation error.
-type OperationError =
- { /// The description of the error that happened in the operation.
- Message : string
- /// The path to the field that produced the error while resolving its value.
- Path : obj [] }
+/// 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 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
+}
/// Contains helpers to build HTTP header sequences to be used in GraphQLProvider Run methods.
module HttpHeaders =
@@ -39,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
@@ -178,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 =
@@ -206,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) =
@@ -228,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
@@ -237,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
@@ -249,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
@@ -293,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
@@ -307,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
@@ -324,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
@@ -354,71 +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 errorMapper = function
+ 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 an Integer."
+ 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())
+ | 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
@@ -434,10 +560,12 @@ 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.
@@ -445,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) =
@@ -468,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 fec874b4..5ba9de7d 100644
--- a/tests/FSharp.Data.GraphQL.IntegrationTests.Server/Schema.fs
+++ b/tests/FSharp.Data.GraphQL.IntegrationTests.Server/Schema.fs
@@ -2,214 +2,277 @@ namespace FSharp.Data.GraphQL.Samples.StarWarsApi
open System
open System.Text
+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")) ])
+ 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 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)
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..f95b44df
--- /dev/null
+++ b/tests/FSharp.Data.GraphQL.IntegrationTests/OperationErrorTests.fs
@@ -0,0 +1,128 @@
+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 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 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 (
+ 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 ()
+
+ 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
+ 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