1
+ /// The MIT License (MIT)
2
+ /// Copyright (c) 2016 Bazinga Technologies Inc
3
+
4
+ module FSharp.Data.GraphQL.Planning
5
+
6
+ open System
7
+ open System.Reflection
8
+ open System.Collections .Generic
9
+ open System.Collections .Concurrent
10
+ open FSharp.Data .GraphQL .Ast
11
+ open FSharp.Data .GraphQL .Types
12
+ open FSharp.Data .GraphQL .Types .Introspection
13
+ open FSharp.Data .GraphQL .Introspection
14
+
15
+ let SchemaMetaFieldDef = Define.Field(
16
+ name = " __schema" ,
17
+ description = " Access the current type schema of this server." ,
18
+ typedef = __ Schema,
19
+ resolve = fun ctx ( _ : obj ) -> ctx.Schema.Introspected)
20
+
21
+ let TypeMetaFieldDef = Define.Field(
22
+ name = " __type" ,
23
+ description = " Request the type information of a single type." ,
24
+ typedef = __ Type,
25
+ args = [
26
+ { Name = " name"
27
+ Description = None
28
+ Type = String
29
+ DefaultValue = None
30
+ ExecuteInput = variableOrElse( coerceStringInput >> Option.map box >> Option.toObj) }
31
+ ],
32
+ resolve = fun ctx ( _ : obj ) ->
33
+ ctx.Schema.Introspected.Types
34
+ |> Seq.find ( fun t -> t.Name = ctx.Arg( " name" ))
35
+ |> IntrospectionTypeRef.Named)
36
+
37
+ let TypeNameMetaFieldDef : FieldDef < obj > = Define.Field(
38
+ name = " __typename" ,
39
+ description = " The name of the current Object type at runtime." ,
40
+ typedef = String,
41
+ resolve = fun ctx ( _ : obj ) -> ctx.ParentType.Name)
42
+
43
+ let private tryFindDef ( schema : ISchema ) ( objdef : ObjectDef ) ( field : Field ) : FieldDef option =
44
+ match field.Name with
45
+ | " __schema" when Object.ReferenceEquals( schema.Query, objdef) -> Some ( upcast SchemaMetaFieldDef)
46
+ | " __type" when Object.ReferenceEquals( schema.Query, objdef) -> Some ( upcast TypeMetaFieldDef)
47
+ | " __typename" -> Some ( upcast TypeNameMetaFieldDef)
48
+ | fieldName -> objdef.Fields |> Map.tryFind fieldName
49
+
50
+ let private coerceVariables ( schema : #ISchema ) ( variables : VariableDefinition list ) ( inputs : Map < string , obj > option ) =
51
+ match inputs with
52
+ | None ->
53
+ variables
54
+ |> List.filter ( fun vardef -> Option.isSome vardef.DefaultValue)
55
+ |> List.fold ( fun acc vardef ->
56
+ let variableName = vardef.VariableName
57
+ Map.add variableName ( coerceVariable schema vardef Map.empty) acc) Map.empty
58
+ | Some vars ->
59
+ variables
60
+ |> List.fold ( fun acc vardef ->
61
+ let variableName = vardef.VariableName
62
+ Map.add variableName ( coerceVariable schema vardef vars) acc) Map.empty
63
+
64
+ let objectData ( ctx : PlanningContext , parentDef : ObjectDef , field : Field , includer : Includer ) : PlanningData =
65
+ match tryFindDef ctx.Schema parentDef field with
66
+ | Some fdef ->
67
+ { Identifier = field.AliasOrName
68
+ ParentDef = parentDef
69
+ Definition = fdef
70
+ Ast = field
71
+ IsNullable = fdef.Type : ? NullableDef
72
+ Include = includer }
73
+ | None ->
74
+ raise ( GraphQLException ( sprintf " No field '%s ' was defined in object definition '%s '" field.Name parentDef.Name))
75
+
76
+ let rec abstractionData ( ctx : PlanningContext ) ( parentDef : AbstractDef ) ( field : Field ) typeCondition includer : Map < string , PlanningData > =
77
+ let objDefs = ctx.Schema.GetPossibleTypes parentDef
78
+ match typeCondition with
79
+ | None ->
80
+ objDefs
81
+ |> Array.choose ( fun objDef ->
82
+ match tryFindDef ctx.Schema objDef field with
83
+ | Some fdef ->
84
+ let data =
85
+ { Identifier = field.AliasOrName
86
+ ParentDef = parentDef
87
+ Definition = fdef
88
+ Ast = field
89
+ IsNullable = fdef.Type :? NullableDef
90
+ Include = includer }
91
+ Some ( objDef.Name, data)
92
+ | None -> None)
93
+ |> Map.ofArray
94
+ | Some typeName ->
95
+ match objDefs |> Array.tryFind ( fun o -> o.Name = typeName) with
96
+ | Some objDef ->
97
+ match tryFindDef ctx.Schema objDef field with
98
+ | Some fdef ->
99
+ let data =
100
+ { Identifier = field.AliasOrName
101
+ ParentDef = parentDef
102
+ Definition = fdef
103
+ Ast = field
104
+ IsNullable = fdef.Type : ? NullableDef
105
+ Include = includer }
106
+ Map.ofList [ objDef.Name, data ]
107
+ | None -> Map.empty
108
+ | None ->
109
+ match ctx.Schema.TryFindType typeName with
110
+ | Some ( Abstract abstractDef) ->
111
+ abstractionData ctx abstractDef field None includer
112
+ | _ ->
113
+ let pname = parentDef :?> NamedDef
114
+ raise ( GraphQLException ( sprintf " There is no object type named '%s ' that is a possible type of '%s '" typeName pname.Name))
115
+
116
+ let private directiveIncluder ( directive : Directive ) : Includer =
117
+ fun variables ->
118
+ match directive.If.Value with
119
+ | Variable vname -> downcast variables.[ vname]
120
+ | other ->
121
+ match coerceBoolInput other with
122
+ | Some s -> s
123
+ | None -> raise (
124
+ GraphQLException ( sprintf " Expected 'if' argument of directive '@%s ' to have boolean value but got %A " directive.Name other))
125
+
126
+ let incl : Includer = fun _ -> true
127
+ let excl : Includer = fun _ -> false
128
+ let private getIncluder ( directives : Directive list ) topIncluder : Includer =
129
+ directives
130
+ |> List.fold ( fun acc directive ->
131
+ match directive.Name with
132
+ | " skip" ->
133
+ fun vars -> acc vars && not ( directiveIncluder directive vars)
134
+ | " include" ->
135
+ fun vars -> acc vars && ( directiveIncluder directive vars)
136
+ | _ -> acc) topIncluder
137
+
138
+ let private doesFragmentTypeApply ( schema : ISchema ) fragment ( objectType : ObjectDef ) =
139
+ match fragment.TypeCondition with
140
+ | None -> true
141
+ | Some typeCondition ->
142
+ match schema.TryFindType typeCondition with
143
+ | None -> false
144
+ | Some conditionalType when conditionalType.Name = objectType.Name -> true
145
+ | Some ( Abstract conditionalType) -> schema.IsPossibleType conditionalType objectType
146
+ | _ -> false
147
+
148
+ let rec private plan ( ctx : PlanningContext ) ( data : PlanningData ) ( typedef : TypeDef ) : ExecutionPlanInfo =
149
+ match typedef with
150
+ | Leaf leafDef -> planLeaf ctx data leafDef
151
+ | Object objDef -> planSelection ctx { data with ParentDef = objDef } data.Ast.SelectionSet ( ref [])
152
+ | Nullable innerDef -> plan ctx { data with IsNullable = true } innerDef
153
+ | List innerDef -> planList ctx data innerDef
154
+ | Abstract abstractDef -> planAbstraction ctx { data with ParentDef = abstractDef } data.Ast.SelectionSet ( ref []) None
155
+
156
+ and private planSelection ( ctx : PlanningContext ) ( data : PlanningData ) ( selectionSet : Selection list ) visitedFragments : ExecutionPlanInfo =
157
+ let parentDef = downcast data.ParentDef
158
+ let plannedFields =
159
+ selectionSet
160
+ |> List.fold( fun ( fields : ExecutionPlanInfo list ) selection ->
161
+ //FIXME: includer is not passed along from top level fragments (both inline and spreads)
162
+ let includer = getIncluder selection.Directives data.Include
163
+ let innerData = { data with Include = includer }
164
+ match selection with
165
+ | Field field ->
166
+ let identifier = field.AliasOrName
167
+ if fields |> List.exists ( fun f -> f.Data.Identifier = identifier)
168
+ then fields
169
+ else
170
+ let data = objectData( ctx, parentDef, field, includer)
171
+ let executionPlan = plan ctx data data.Definition.Type
172
+ fields @ [ executionPlan] // unfortunatelly, order matters here
173
+ | FragmentSpread spread ->
174
+ let spreadName = spread.Name
175
+ if ! visitedFragments |> List.exists ( fun name -> name = spreadName)
176
+ then fields // fragment already found
177
+ else
178
+ visitedFragments := spreadName::! visitedFragments
179
+ match ctx.Document.Definitions |> List.tryFind ( function FragmentDefinition f -> f.Name.Value = spreadName | _ -> false ) with
180
+ | Some ( FragmentDefinition fragment) when doesFragmentTypeApply ctx.Schema fragment parentDef ->
181
+ // retrieve fragment data just as it was normal selection set
182
+ let ( SelectFields ( _ , fragmentFields )) = planSelection ctx innerData fragment.SelectionSet visitedFragments
183
+ // filter out already existing fields
184
+ List.mergeBy ( fun field -> field.Data.Identifier) fields fragmentFields
185
+ | _ -> fields
186
+ | InlineFragment fragment when doesFragmentTypeApply ctx.Schema fragment parentDef ->
187
+ // retrieve fragment data just as it was normal selection set
188
+ let ( SelectFields ( _ , fragmentFields )) = planSelection ctx innerData fragment.SelectionSet visitedFragments
189
+ // filter out already existing fields
190
+ List.mergeBy ( fun field -> field.Data.Identifier) fields fragmentFields
191
+ | _ -> fields
192
+ ) []
193
+ SelectFields( data, plannedFields)
194
+
195
+ and private planList ( ctx : PlanningContext ) ( data : PlanningData ) ( innerDef : TypeDef ) : ExecutionPlanInfo =
196
+ ResolveCollection( data, plan ctx data innerDef)
197
+
198
+ and private planLeaf ( ctx : PlanningContext ) ( data : PlanningData ) ( leafDef : LeafDef ) : ExecutionPlanInfo =
199
+ ResolveValue( data)
200
+
201
+ and private planAbstraction ( ctx : PlanningContext ) ( data : PlanningData ) ( selectionSet : Selection list ) visitedFragments typeCondition : ExecutionPlanInfo =
202
+ let parentDef = downcast data.ParentDef
203
+ let plannedTypeFields =
204
+ selectionSet
205
+ |> List.fold( fun ( fields : Map < string , ExecutionPlanInfo list >) selection ->
206
+ let includer = getIncluder selection.Directives data.Include
207
+ let innerData = { data with Include = includer }
208
+ match selection with
209
+ | Field field ->
210
+ abstractionData ctx parentDef field typeCondition includer
211
+ |> Map.map ( fun typeName data -> [ plan ctx data data.Definition.Type ])
212
+ |> Map.merge ( fun typeName oldVal newVal -> oldVal @ newVal) fields
213
+ | FragmentSpread spread ->
214
+ let spreadName = spread.Name
215
+ if ! visitedFragments |> List.exists ( fun name -> name = spreadName)
216
+ then fields // fragment already found
217
+ else
218
+ visitedFragments := spreadName::! visitedFragments
219
+ match ctx.Document.Definitions |> List.tryFind ( function FragmentDefinition f -> f.Name.Value = spreadName | _ -> false ) with
220
+ | Some ( FragmentDefinition fragment) ->
221
+ // retrieve fragment data just as it was normal selection set
222
+ let ( ResolveAbstraction ( _ , fragmentFields )) = planAbstraction ctx innerData fragment.SelectionSet visitedFragments fragment.TypeCondition
223
+ // filter out already existing fields
224
+ Map.merge ( fun typeName oldVal newVal -> oldVal @ newVal) fields fragmentFields
225
+ | _ -> fields
226
+ | InlineFragment fragment ->
227
+ // retrieve fragment data just as it was normal selection set
228
+ let ( ResolveAbstraction ( _ , fragmentFields )) = planAbstraction ctx innerData fragment.SelectionSet visitedFragments fragment.TypeCondition
229
+ // filter out already existing fields
230
+ Map.merge ( fun typeName oldVal newVal -> oldVal @ newVal) fields fragmentFields
231
+ | _ -> fields
232
+ ) Map.empty
233
+ ResolveAbstraction( data, plannedTypeFields)
234
+
235
+ let planOperation ( ctx : PlanningContext ) ( operation : OperationDefinition ) : ExecutionPlan =
236
+ let data = {
237
+ Identifier = null ;
238
+ Ast = Unchecked.defaultof< Field>
239
+ IsNullable = false
240
+ ParentDef = ctx.RootDef
241
+ Definition = Unchecked.defaultof< FieldDef>
242
+ Include = incl }
243
+ let ( SelectFields ( _ , topFields )) = planSelection ctx data operation.SelectionSet ( ref [])
244
+ match operation.OperationType with
245
+ | Query ->
246
+ { Operation = operation
247
+ Fields = topFields
248
+ RootDef = ctx.Schema.Query
249
+ Strategy = Parallel }
250
+ | Mutation ->
251
+ match ctx.Schema.Mutation with
252
+ | Some mutationDef ->
253
+ { Operation = operation
254
+ Fields = topFields
255
+ RootDef = mutationDef
256
+ Strategy = Serial }
257
+ | None ->
258
+ raise ( GraphQLException " Tried to execute a GraphQL mutation on schema with no mutation type defined" )
0 commit comments