Skip to content

Commit

Permalink
Add support for generic IDictionary<string,obj> query inputs - relate…
Browse files Browse the repository at this point in the history
  • Loading branch information
pkese committed Apr 6, 2024
1 parent cf60e89 commit 1d24f47
Show file tree
Hide file tree
Showing 5 changed files with 136 additions and 9 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net6.0</TargetFramework>
<TargetFramework>net8.0</TargetFramework>
<EnableDefaultCompileItems>false</EnableDefaultCompileItems>
</PropertyGroup>

Expand Down
51 changes: 49 additions & 2 deletions samples/star-wars-api/Schema.fs
Original file line number Diff line number Diff line change
Expand Up @@ -153,12 +153,30 @@ module Schema =
| Human _ -> upcast HumanType
| Droid _ -> upcast DroidType)
)
(*
and CharacterInfo =
Define.Interface<obj> (
name = "CharacterInfo",
description = "A character common info.",
fieldsFn =
fun () ->
[ Define.Field ("id", StringType, "The id of the character.")
Define.Field ("name", Nullable StringType, "The name of the character.")
Define.Field ("appearsIn", ListOf EpisodeType, "Which movies they appear in.")
Define.Field (
"friends",
ConnectionOf CharacterType,
"The friends of the human, or an empty list if they have none." )
]
)
*)

and HumanType : ObjectDef<Human> =
Define.Object<Human> (
name = "Human",
description = "A humanoid creature in the Star Wars universe.",
isTypeOf = (fun o -> o :? Human),
//interfaces = [ CharacterInfo ],
fieldsFn =
fun () ->
[ Define.Field ("id", StringType, "The id of the human.", (fun _ (h : Human) -> h.Id))
Expand Down Expand Up @@ -206,7 +224,9 @@ module Schema =
con
)
Define.Field ("appearsIn", ListOf EpisodeType, "Which movies they appear in.", (fun _ (h : Human) -> h.AppearsIn))
Define.Field ("homePlanet", Nullable StringType, "The home planet of the human, or null if unknown.", (fun _ h -> h.HomePlanet)) ]
Define.Field ("homePlanet", Nullable StringType, "The home planet of the human, or null if unknown.", (fun _ h -> h.HomePlanet))
Define.Field ("planet", Nullable PlanetType, "The home planet of the human, or null if unknown.", (fun _ h -> h.HomePlanet |> Option.bind getPlanet ))
]
)

and DroidType =
Expand Down Expand Up @@ -250,6 +270,30 @@ module Schema =
fieldsFn = fun () -> [ Define.Field ("requestId", StringType, "The ID of the client.", (fun _ (r : Root) -> r.RequestId)) ]
)

//Define.Input ("range", Nullable (Define.InputObject<Collections.Generic.KeyValuePair<string,int>>("Range", [
//Define.Input ("range", Nullable (Define.InputObject<Range>("Range", [
type Range = { Min : int; Max : int }
let randoms =
let inputs = [
Define.Input ("count", IntType)
//Define.Input ("range", Nullable (Define.InputObject<System.Text.Json.JsonProperty>("Range", [
//Define.Input ("range", Nullable (Define.InputObject<System.Collections.Generic.Dictionary<string,obj>>("Range", [
Define.Input ("range", Nullable (Define.InputObject<System.Dynamic.ExpandoObject>("Range", [
//Define.Input ("range", Nullable (Define.InputObject<System.Collections.Generic.KeyValuePair<string,int>>("Range", [
Define.Input ("min", IntType)
Define.Input ("max", IntType)
])))
]
let renderRandoms (ctx:ResolveFieldContext) (r:Root) =
printfn "Random called with count:%A and range:%A" (ctx.Arg "count") (ctx.Arg "range")
//let range = ctx.Arg "range" |> unbox<System.Dynamic.DynamicObject>
//printfn "members: %A" (range.GetDynamicMemberNames())
//range.GetDynamicMemberNames() |> Seq.iter (printfn "Dynamic member: %A")
[ for i in 1..ctx.Arg("count") -> System.Random.Shared.Next() ]

Define.Field ("random", ListOf IntType, "Render random numbers", inputs, renderRandoms)


let Query =
let inputs = [ Define.Input ("id", StringType) ]
Define.Object<Root> (
Expand All @@ -258,7 +302,10 @@ module Schema =
[ Define.Field ("hero", Nullable HumanType, "Gets human hero", inputs, fun ctx _ -> getHuman (ctx.Arg ("id")))
Define.Field ("droid", Nullable DroidType, "Gets droid", inputs, (fun ctx _ -> getDroid (ctx.Arg ("id"))))
Define.Field ("planet", Nullable PlanetType, "Gets planet", inputs, fun ctx _ -> getPlanet (ctx.Arg ("id")))
Define.Field ("characters", ListOf CharacterType, "Gets characters", (fun _ _ -> characters)) ]
Define.Field ("characters", ListOf CharacterType, "Gets characters", (fun _ _ -> characters))
randoms
]

)

let Subscription =
Expand Down
4 changes: 2 additions & 2 deletions samples/star-wars-api/appsettings.json
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
{
"Logging": {
"IncludeScopes": false,
"LogLevel": {
"Default": "Warning"
"Default": "Trace",
"Microsoft.Hosting": "Trace"
}
}
}
60 changes: 60 additions & 0 deletions src/FSharp.Data.GraphQL.Server/ReflectionHelper.fs
Original file line number Diff line number Diff line change
Expand Up @@ -140,8 +140,68 @@ module internal ReflectionHelper =

let isPrameterMandatory = not << isParameterOptional


(*
let makeConstructor (fields: string[]) =
{ new ConstructorInfo() as this with
let typ = typeof<Dynamic.DynamicObject>
let ctor =
typ.GetConstructors(BindingFlags.NonPublic|||BindingFlags.Public|||BindingFlags.Instance)
|> Seq.filter (fun ctor -> ctor.GetParameters().Length = 0)
|> Seq.head
override _.GetParameters() = [||]
override _.Attributes = ctor.Attributes
override _.MethodHandle = ctor.MethodHandle
override _.GetMethodImplementationFlags() = ctor.GetMethodImplementationFlags()
member _.Invoke(args: obj []) = Activator.CreateInstance(typeof<obj>, args)
member _.Name = "DynamicConstructorInfo"
member _.DeclaringType = typeof<obj>
override _.Invoke(bindingFlags, binder, args, cultureInfo) = obj()
}
*)

type DynamicConstructorInfo(typ:System.Type, fields: string[]) =
inherit ConstructorInfo()
let ctor =
typ.GetConstructors(BindingFlags.NonPublic|||BindingFlags.Public|||BindingFlags.Instance)
|> Seq.filter (fun ctor -> ctor.GetParameters().Length = 0)
|> Seq.head
override _.GetParameters() =
printfn "DynamicConstructorInfo.GetParameters() -> %A" (fields |> String.concat ",")
fields
|> Array.mapi (fun i fName ->
{ new ParameterInfo() with
member _.Name = fName
member _.Position = i
member _.ParameterType = typeof<obj>
member _.Attributes = ParameterAttributes.Optional
}
)
override _.Attributes = ctor.Attributes
override _.MethodHandle = ctor.MethodHandle
override _.GetMethodImplementationFlags() = ctor.GetMethodImplementationFlags()
override _.GetCustomAttributes(_inherit) = typ.GetCustomAttributes(_inherit)
override _.GetCustomAttributes(x, _inherit) = typ.GetCustomAttributes(x, _inherit)
override _.IsDefined(x, _inherit) = typ.IsDefined(x, _inherit)
override _.Name = "DynamicConstructorInfo"
override _.DeclaringType = typ.DeclaringType
override _.ReflectedType = typ.ReflectedType
member this.Invoke(args: obj []) =
printfn "DynamicConstructorInfo.Invoke(%A) nArgs=%d nFields=%d" args args.Length fields.Length
let o = Activator.CreateInstance(typ)
let dict = o :?> IDictionary<string, obj>
(fields, args)
||> Seq.iter2 (fun k v -> dict.Add(k, v))
o
override this.Invoke(bindingFlags, binder, args, cultureInfo) = this.Invoke(args)
override this.Invoke(o, bindingFlags, binder, args, cultureInfo) = this.Invoke(bindingFlags, binder, args, cultureInfo)

let matchConstructor (t: Type) (fields: string []) =
if FSharpType.IsRecord(t, true) then FSharpValue.PreComputeRecordConstructorInfo(t, true)
//else if t = typeof<Dynamic.DynamicObject> then
else if typeof<Collections.Generic.IDictionary<string,obj>>.IsAssignableFrom(t) then
eprintfn "matched DynamicConstructorInfo for type %A" t
upcast DynamicConstructorInfo(t, fields)
else
let constructors = t.GetConstructors(BindingFlags.NonPublic|||BindingFlags.Public|||BindingFlags.Instance)
let inputFieldNames =
Expand Down
28 changes: 24 additions & 4 deletions src/FSharp.Data.GraphQL.Server/Values.fs
Original file line number Diff line number Diff line change
Expand Up @@ -89,10 +89,30 @@ let rec internal compileByType (inputObjectPath: FieldPath) (inputSource : Input

| InputObject objDef ->
let objtype = objDef.Type
let ctor = ReflectionHelper.matchConstructor objtype (objDef.Fields |> Array.map (fun x -> x.Name))
let (constructor : obj[] -> obj), (parameterInfos : Reflection.ParameterInfo[]) =
if typeof<IDictionary<string,obj>>.IsAssignableFrom(objtype) then
let parameterInfos = [|
for f in objDef.Fields ->
{ new Reflection.ParameterInfo() with
member _.Name = f.Name
member _.ParameterType = f.TypeDef.Type
member _.Attributes = Reflection.ParameterAttributes.Optional
}
|]
let constructor (args:obj[]) =
let o = Activator.CreateInstance(objtype)
let dict = o :?> IDictionary<string, obj>
for fld,arg in Seq.zip objDef.Fields args do
dict.Add(fld.Name, arg)
box o
constructor, parameterInfos
else
let ctor = ReflectionHelper.matchConstructor objtype (objDef.Fields |> Array.map (fun x -> x.Name))
ctor.Invoke, ctor.GetParameters()


let struct (mapper, nullableMismatchParameters, missingParameters) =
ctor.GetParameters ()
parameterInfos
|> Array.fold (
fun
struct(all: ResizeArray<_>, areNullable: HashSet<_>, missing: HashSet<_>)
Expand Down Expand Up @@ -170,7 +190,7 @@ let rec internal compileByType (inputObjectPath: FieldPath) (inputSource : Input

let! args = argResults |> splitSeqErrorsList

let instance = ctor.Invoke args
let instance = constructor args
do! objDef.Validator instance
|> ValidationResult.mapErrors (fun err -> err |> mapInputObjectError inputSource inputObjectPath originalInputDef)
return instance
Expand All @@ -197,7 +217,7 @@ let rec internal compileByType (inputObjectPath: FieldPath) (inputSource : Input

let! args = argResults |> splitSeqErrorsList

let instance = ctor.Invoke args
let instance = constructor args
do! objDef.Validator instance
|> ValidationResult.mapErrors (fun err -> err |> mapInputObjectError inputSource inputObjectPath originalInputDef)
return instance
Expand Down

0 comments on commit 1d24f47

Please sign in to comment.