forked from TOTBWF/FSharp.Data.GraphQL
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathExecutor.fs
More file actions
142 lines (129 loc) · 8.51 KB
/
Copy pathExecutor.fs
File metadata and controls
142 lines (129 loc) · 8.51 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
namespace FSharp.Data.GraphQL
open System
open System.Collections.Generic
open FSharp.Data.GraphQL.Types
open FSharp.Data.GraphQL.Execution
open FSharp.Data.GraphQL.Ast
open FSharp.Data.GraphQL.Parser
open FSharp.Data.GraphQL.Types.Patterns
open FSharp.Data.GraphQL.Planning
type Executor<'Root> (schema: ISchema<'Root>) =
let fieldExecuteMap = FieldExecuteMap()
//FIXME: for some reason static do or do invocation in module doesn't work
// for this reason we're compiling executors as part of identifier evaluation
let __done =
// we don't need to know possible types at this point
fieldExecuteMap.SetExecute("",
"__schema",
compileField SchemaMetaFieldDef)
fieldExecuteMap.SetExecute("",
"__type",
compileField TypeMetaFieldDef )
fieldExecuteMap.SetExecute("",
"__typename",
compileField TypeNameMetaFieldDef )
do
compileSchema schema.TypeMap fieldExecuteMap schema.SubscriptionProvider
match Validation.validate schema.TypeMap with
| Validation.Success -> ()
| Validation.Error errors -> raise (GraphQLException (System.String.Join("\n", errors)))
let eval(executionPlan: ExecutionPlan, data: 'Root option, variables: Map<string, obj>): Async<GQLResponse> =
let prepareOutput res =
let prepareData data (errs: Error list) =
let parsedErrors =
errs
|> List.map(fun (message, path) -> NameValueLookup.ofList [ "message", upcast message; "path", upcast path])
match parsedErrors with
| [] -> NameValueLookup.ofList [ "documentId", box executionPlan.DocumentId ; "data", upcast data ] :> Output
| errors -> NameValueLookup.ofList [ "documentId", box executionPlan.DocumentId ; "data", upcast data ; "errors", upcast errors ] :> Output
match res with
| Direct(data, errors) -> Direct(prepareData data errors, errors)
| Deferred(data, errors, deferred) -> Deferred(prepareData data errors, errors, deferred)
| Stream(stream) -> Stream(stream)
async {
try
let errors = System.Collections.Concurrent.ConcurrentBag<exn>()
let rootObj = data |> Option.map box |> Option.toObj
let! res = evaluate schema executionPlan variables rootObj errors fieldExecuteMap
return prepareOutput res
with
| ex ->
let msg = ex.ToString()
return prepareOutput(Direct(new Dictionary<string, obj>() :> Output, [msg, []]))
}
/// <summary>
/// Asynchronously executes parsed GraphQL query AST. Returned value is a readonly dictionary consisting of following top level entries:
/// - `documentId`: unique identifier of current document's AST
/// - `data`: GraphQL response matching the structure provided in GraphQL query string
/// - `errors` (oprional): contains a list of errors that occurred while executing a GraphQL operation
/// </summary>
/// <param name="ast">Parsed GraphQL query string.</param>
/// <param name="data">Optional object provided as a root to all top level field resolvers</param>
/// <param name="variables">Map of all variable values provided by the client request.</param>
/// <param name="operationName">In case when document consists of many operations, this field describes which of them to execute.</param>
member this.AsyncExecute(ast: Document, ?data: 'Root, ?variables: Map<string, obj>, ?operationName: string): Async<GQLResponse> =
let executionPlan =
match operationName with
| Some opname -> this.CreateExecutionPlan(ast, opname)
| None -> this.CreateExecutionPlan(ast)
eval(executionPlan, data, defaultArg variables Map.empty)
/// <summary>
/// Asynchronously executes unparsed GraphQL query AST. Returned value is a readonly dictionary consisting of following top level entries:
/// - `documentId`: unique identifier of current document's AST
/// - `data`: GraphQL response matching the structure provided in GraphQL query string
/// - `errors (oprional): contains a list of errors that occurred while executing a GraphQL operation
/// </summary>
/// <param name="ast">Parsed GraphQL query string.</param>
/// <param name="data">Optional object provided as a root to all top level field resolvers</param>
/// <param name="variables">Map of all variable values provided by the client request.</param>
/// <param name="operationName">In case when document consists of many operations, this field describes which of them to execute.</param>
member this.AsyncExecute(queryOrMutation: string, ?data: 'Root, ?variables: Map<string, obj>, ?operationName: string): Async<GQLResponse> =
let ast = parse queryOrMutation
let executionPlan =
match operationName with
| Some opname -> this.CreateExecutionPlan(ast, opname)
| None -> this.CreateExecutionPlan(ast)
eval(executionPlan, data, defaultArg variables Map.empty)
/// <summary>
/// Asynchronously executes a provided execution plan. In case of repetitive queries, execution plan may be preprocessed
/// and cached using `documentId` as an identifier.
/// Returned value is a readonly dictionary consisting of following top level entries:
/// - `documentId`: unique identifier of current document's AST, it can be used as a key/identifier of ExecutionPlan as well
/// - `data`: GraphQL response matching the structure provided in GraphQL query string
/// - `errors (oprional): contains a list of errors that occurred while executing a GraphQL operation
/// </summary>
/// <param name="ast">Parsed GraphQL query string.</param>
/// <param name="data">Optional object provided as a root to all top level field resolvers</param>
/// <param name="variables">Map of all variable values provided by the client request.</param>
/// <param name="operationName">In case when document consists of many operations, this field describes which of them to execute.</param>
member this.AsyncExecute(executionPlan: ExecutionPlan, ?data: 'Root, ?variables: Map<string, obj>): Async<GQLResponse> =
eval(executionPlan, data, defaultArg variables Map.empty)
/// Creates an execution plan for provided GraphQL document AST without
/// executing it. This is useful in cases when you have the same query executed
/// multiple times with different parameters. In that case, query can be used
/// to construct execution plan, which then is cached (using DocumentId as a key) and reused when needed.
member this.CreateExecutionPlan(ast: Document, ?operationName: string): ExecutionPlan =
match findOperation ast operationName with
| Some operation ->
let rootDef =
match operation.OperationType with
| Query -> schema.Query
| Mutation ->
match schema.Mutation with
| Some m -> m
| None -> raise (GraphQLException "Operation to be executed is of type mutation, but no mutation root object was defined in current schema")
| Subscription ->
match schema.Subscription with
| Some s -> upcast s
| None -> raise (GraphQLException "Operations to be executed is of type subscription, but no subscription root object was defined in the current schema")
let planningCtx = { Schema = schema; RootDef = rootDef; Document = ast }
planOperation (ast.GetHashCode()) planningCtx operation
| None -> raise (GraphQLException "No operation with specified name has been found for provided document")
/// Creates an execution plan for provided GraphQL query string without
/// executing it. This is useful in cases when you have the same query executed
/// multiple times with different parameters. In that case, query can be used
/// to construct execution plan, which then is cached (using DocumentId as a key) and reused when needed.
member this.CreateExecutionPlan(queryOrMutation: string, ?operationName: string) =
match operationName with
| None -> this.CreateExecutionPlan(parse queryOrMutation)
| Some o -> this.CreateExecutionPlan(parse queryOrMutation, o)