Skip to content
Permalink

Comparing changes

This is a direct comparison between two commits made in this repository or its related repositories. View the default comparison for this range or learn more about diff comparisons.

Open a pull request

Create a new pull request by comparing changes across two branches. If you need to, you can also . Learn more about diff comparisons here.
base repository: fsprojects/FSharp.Data.GraphQL
Failed to load repositories. Confirm that selected base ref is valid, then try again.
Loading
base: a024d30f5108834002e327b605f08bd40d23578d
Choose a base ref
..
head repository: fsprojects/FSharp.Data.GraphQL
Failed to load repositories. Confirm that selected head ref is valid, then try again.
Loading
compare: 48c7715c9e4c8501c6be4659190c16e648307c8b
Choose a head ref
Showing with 197 additions and 86 deletions.
  1. +102 −45 src/FSharp.Data.GraphQL.Server/Values.fs
  2. +87 −33 tests/FSharp.Data.GraphQL.Tests/ResolveTests.fs
  3. +8 −8 tests/FSharp.Data.GraphQL.Tests/VariablesTests.fs
147 changes: 102 additions & 45 deletions src/FSharp.Data.GraphQL.Server/Values.fs
Original file line number Diff line number Diff line change
@@ -14,40 +14,54 @@ open Microsoft.FSharp.Reflection

/// Tries to convert type defined in AST into one of the type defs known in schema.
let inline tryConvertAst schema ast =
let rec convert isNullable (schema: ISchema) (ast: InputType) : TypeDef option =
let rec convert isNullable (schema : ISchema) (ast : InputType) : TypeDef option =
match ast with
| NamedType name ->
match schema.TryFindType name with
| Some namedDef ->
Some (if isNullable then upcast namedDef.MakeNullable() else upcast namedDef)
Some (
if isNullable then
upcast namedDef.MakeNullable ()
else
upcast namedDef
)
| None -> None
| ListType inner ->
convert true schema inner
|> Option.map (fun i ->
if isNullable
then upcast i.MakeList().MakeNullable()
else upcast i.MakeList())
| NonNullType inner ->
convert false schema inner
if isNullable then
upcast i.MakeList().MakeNullable ()
else
upcast i.MakeList ())
| NonNullType inner -> convert false schema inner

convert true schema ast

let inline private notAssignableMsg (innerDef: InputDef) value : string =
let inline private notAssignableMsg (innerDef : InputDef) value : string =
sprintf "value of type %s is not assignable from %s" innerDef.Type.Name (value.GetType().Name)

let rec internal compileByType (errMsg: string) (inputDef: InputDef): ExecuteInput =
let rec internal compileByType (errMsg : string) (inputDef : InputDef) : ExecuteInput =
match inputDef with
| Scalar scalardef ->
variableOrElse (scalardef.CoerceInput >> Option.toObj)
| Scalar scalardef -> variableOrElse (scalardef.CoerceInput >> Option.toObj)
| InputObject objdef ->
let objtype = objdef.Type
let ctor = ReflectionHelper.matchConstructor objtype (objdef.Fields |> Array.map (fun x -> x.Name))

let mapper =
ctor.GetParameters()
|> Array.map(fun param ->
match objdef.Fields |> Array.tryFind(fun field -> field.Name = param.Name) with
ctor.GetParameters ()
|> Array.map (fun param ->
match
objdef.Fields
|> Array.tryFind (fun field -> field.Name = param.Name)
with
| Some x -> x
| None ->
failwithf "Input object '%s' refers to type '%O', but constructor parameter '%s' doesn't match any of the defined input fields" objdef.Name objtype param.Name)
failwithf
"Input object '%s' refers to type '%O', but constructor parameter '%s' doesn't match any of the defined input fields"
objdef.Name
objtype
param.Name)

fun value variables ->
match value with
| ObjectValue props ->
@@ -57,7 +71,8 @@ let rec internal compileByType (errMsg: string) (inputDef: InputDef): ExecuteInp
match Map.tryFind field.Name props with
| None -> null
| Some prop -> field.ExecuteInput prop variables)
let instance = ctor.Invoke(args)

let instance = ctor.Invoke (args)
instance
| Variable variableName ->
match Map.tryFind variableName variables with
@@ -73,6 +88,7 @@ let rec internal compileByType (errMsg: string) (inputDef: InputDef): ExecuteInp
match value with
| ListValue list ->
let mappedValues = list |> List.map (fun value -> inner value variables)

if isArray then
ReflectionHelper.arrayOfList innerdef.Type mappedValues
else
@@ -81,21 +97,30 @@ let rec internal compileByType (errMsg: string) (inputDef: InputDef): ExecuteInp
| _ ->
// try to construct a list from single element
let single = inner value variables
if single = null then null else
if isArray then ReflectionHelper.arrayOfList innerdef.Type [single]
else cons single nil

if single = null then
null
else if isArray then
ReflectionHelper.arrayOfList innerdef.Type [ single ]
else
cons single nil
| Nullable (Input innerdef) ->
let inner = compileByType errMsg innerdef
let some, none, _ = ReflectionHelper.optionOfType innerdef.Type

fun variables value ->
let i = inner variables value

match i with
| null -> none
| coerced ->
let c = some coerced
if c <> null then c
else raise(GraphQLException (errMsg + notAssignableMsg innerdef coerced))

if c <> null then
c
else
raise
<| GraphQLException (errMsg + notAssignableMsg innerdef coerced)
| Enum enumdef ->
fun value variables ->
match value with
@@ -105,35 +130,50 @@ let rec internal compileByType (errMsg: string) (inputDef: InputDef): ExecuteInp
| None -> failwithf "Variable '%s' not supplied.\nVariables: %A" variableName variables
| _ ->
let coerced = coerceEnumInput value

match coerced with
| None -> null
| Some s -> ReflectionHelper.parseUnion enumdef.Type s
| Some s ->
enumdef.Options
|> Seq.tryFind (fun v -> v.Name = s)
|> Option.map (fun x -> x.Value :?> _)
|> Option.defaultWith (fun () -> ReflectionHelper.parseUnion enumdef.Type s)
| _ -> failwithf "Unexpected value of inputDef: %O" inputDef

let rec private coerceVariableValue isNullable typedef (vardef: VarDef) (input: obj) (errMsg: string) : obj =
let rec private coerceVariableValue isNullable typedef (vardef : VarDef) (input : obj) (errMsg : string) : obj =
match typedef with
| Scalar scalardef ->
match scalardef.CoerceValue input with
| None when isNullable -> null
| None ->
raise (GraphQLException <| errMsg + (sprintf "expected value of type %s but got None" scalardef.Name))
raise (
GraphQLException
<| $"%s{errMsg}expected value of type '%s{scalardef.Name}' but got 'None'."
)
| Some res -> res
| Nullable (Input innerdef) ->
let some, none, innerValue = ReflectionHelper.optionOfType innerdef.Type
let input = innerValue input
let coerced = coerceVariableValue true innerdef vardef input errMsg
if coerced <> null
then

if coerced <> null then
let s = some coerced
if s <> null
then s
else raise (GraphQLException <| errMsg + (sprintf "value of type %O is not assignable from %O" innerdef.Type (coerced.GetType())))
else none

if s <> null then
s
else
raise
<| GraphQLException ($"%s{errMsg}value of type '%O{innerdef.Type}' is not assignable from '%O{coerced.GetType ()}'.")
else
none
| List (Input innerdef) ->
let cons, nil = ReflectionHelper.listOfType innerdef.Type

match input with
| null when isNullable -> null
| null -> raise(GraphQLException <| errMsg + (sprintf "expected value of type %s, but no value was found." (vardef.TypeDef.ToString())))
| null ->
raise
<| GraphQLException ($"%s{errMsg}expected value of type '%s{vardef.TypeDef.ToString ()}', but no value was found.")
// special case - while single values should be wrapped with a list in this scenario,
// string would be treat as IEnumerable and coerced into a list of chars
| :? string as s ->
@@ -148,37 +188,52 @@ let rec private coerceVariableValue isNullable typedef (vardef: VarDef) (input:
|> Seq.toList
|> List.rev
|> List.fold (fun acc coerced -> cons coerced acc) nil

mapped
| other -> raise (GraphQLException <| errMsg + (sprintf "Cannot coerce value of type '%O' to list." (other.GetType())))
| InputObject objdef ->
coerceVariableInputObject objdef vardef input (errMsg + (sprintf "in input object '%s': " objdef.Name))
| other ->
raise
<| GraphQLException ($"{errMsg}Cannot coerce value of type '%O{other.GetType ()}' to list.")
| InputObject objdef -> coerceVariableInputObject objdef vardef input (errMsg + (sprintf "in input object '%s': " objdef.Name))
| Enum enumdef ->
match input with
| :? string as s ->
ReflectionHelper.parseUnion enumdef.Type s
| :? string as s -> ReflectionHelper.parseUnion enumdef.Type s
| null when isNullable -> null
| null -> raise(GraphQLException <| errMsg + (sprintf "Expected Enum '%s', but no value was found." enumdef.Name))
| u when FSharpType.IsUnion(enumdef.Type) && enumdef.Type = input.GetType() -> u
| o when Enum.IsDefined(enumdef.Type, o) -> o
| null ->
raise
<| GraphQLException ($"%s{errMsg}Expected Enum '%s{enumdef.Name}', but no value was found.")

| u when
FSharpType.IsUnion (enumdef.Type)
&& enumdef.Type = input.GetType ()
->
u
| o when Enum.IsDefined (enumdef.Type, o) -> o
| _ ->
raise (GraphQLException <| errMsg + (sprintf "Cannot coerce value of type '%O' to type Enum '%s'" (input.GetType()) enumdef.Name))
| _ -> raise (GraphQLException <| errMsg + "Only Scalars, Nullables, Lists and InputObjects are valid type definitions.")
raise (
GraphQLException
<| $"%s{errMsg}Cannot coerce value of type '%O{input.GetType ()}' to type Enum '%s{enumdef.Name}'."
)
| _ ->
raise
<| GraphQLException ($"%s{errMsg}Only Scalars, Nullables, Lists, and InputObjects are valid type definitions.")

and private coerceVariableInputObject (objdef) (vardef: VarDef) (input: obj) errMsg =
and private coerceVariableInputObject (objdef) (vardef : VarDef) (input : obj) errMsg =
//TODO: this should be eventually coerced to complex object
match input with
| :? Map<string, obj> as map ->
let mapped =
objdef.Fields
|> Array.map (fun field ->
let valueFound = Map.tryFind field.Name map |> Option.toObj
(field.Name, coerceVariableValue false field.TypeDef vardef valueFound (errMsg + (sprintf "in field '%s': " field.Name))))
(field.Name, coerceVariableValue false field.TypeDef vardef valueFound $"%s{errMsg}in field '%s{field.Name}': "))
|> Map.ofArray

upcast mapped
| _ -> input

let internal coerceVariable (vardef: VarDef) (inputs) =
let internal coerceVariable (vardef : VarDef) (inputs) =
let vname = vardef.Name

match Map.tryFind vname inputs with
| None ->
match vardef.DefaultValue with
@@ -189,5 +244,7 @@ let internal coerceVariable (vardef: VarDef) (inputs) =
| None ->
match vardef.TypeDef with
| Nullable _ -> null
| _ -> raise (GraphQLException (sprintf "Variable '$%s' of required type %s has no value provided." vname (vardef.TypeDef.ToString())))
| _ ->
raise
<| GraphQLException ($"Variable '$%s{vname}' of required type '%s{vardef.TypeDef.ToString ()}' has no value provided.")
| Some input -> coerceVariableValue false vardef.TypeDef vardef input (sprintf "Variable '$%s': " vname)
Loading