1 package executor
2
3 import (
4 "context"
5
6 "github.com/vektah/gqlparser/v2/ast"
7 "github.com/vektah/gqlparser/v2/gqlerror"
8 "github.com/vektah/gqlparser/v2/parser"
9 "github.com/vektah/gqlparser/v2/validator"
10
11 "github.com/99designs/gqlgen/graphql"
12 "github.com/99designs/gqlgen/graphql/errcode"
13 )
14
15
16 type Executor struct {
17 es graphql.ExecutableSchema
18 extensions []graphql.HandlerExtension
19 ext extensions
20
21 errorPresenter graphql.ErrorPresenterFunc
22 recoverFunc graphql.RecoverFunc
23 queryCache graphql.Cache
24 }
25
26 var _ graphql.GraphExecutor = &Executor{}
27
28
29
30 func New(es graphql.ExecutableSchema) *Executor {
31 e := &Executor{
32 es: es,
33 errorPresenter: graphql.DefaultErrorPresenter,
34 recoverFunc: graphql.DefaultRecover,
35 queryCache: graphql.NoCache{},
36 ext: processExtensions(nil),
37 }
38 return e
39 }
40
41 func (e *Executor) CreateOperationContext(
42 ctx context.Context,
43 params *graphql.RawParams,
44 ) (*graphql.OperationContext, gqlerror.List) {
45 rc := &graphql.OperationContext{
46 DisableIntrospection: true,
47 RecoverFunc: e.recoverFunc,
48 ResolverMiddleware: e.ext.fieldMiddleware,
49 RootResolverMiddleware: e.ext.rootFieldMiddleware,
50 Stats: graphql.Stats{
51 Read: params.ReadTime,
52 OperationStart: graphql.GetStartTime(ctx),
53 },
54 }
55 ctx = graphql.WithOperationContext(ctx, rc)
56
57 for _, p := range e.ext.operationParameterMutators {
58 if err := p.MutateOperationParameters(ctx, params); err != nil {
59 return rc, gqlerror.List{err}
60 }
61 }
62
63 rc.RawQuery = params.Query
64 rc.OperationName = params.OperationName
65 rc.Headers = params.Headers
66
67 var listErr gqlerror.List
68 rc.Doc, listErr = e.parseQuery(ctx, &rc.Stats, params.Query)
69 if len(listErr) != 0 {
70 return rc, listErr
71 }
72
73 rc.Operation = rc.Doc.Operations.ForName(params.OperationName)
74 if rc.Operation == nil {
75 err := gqlerror.Errorf("operation %s not found", params.OperationName)
76 errcode.Set(err, errcode.ValidationFailed)
77 return rc, gqlerror.List{err}
78 }
79
80 var err error
81 rc.Variables, err = validator.VariableValues(e.es.Schema(), rc.Operation, params.Variables)
82
83 if err != nil {
84 gqlErr, ok := err.(*gqlerror.Error)
85 if ok {
86 errcode.Set(gqlErr, errcode.ValidationFailed)
87 return rc, gqlerror.List{gqlErr}
88 }
89 }
90 rc.Stats.Validation.End = graphql.Now()
91
92 for _, p := range e.ext.operationContextMutators {
93 if err := p.MutateOperationContext(ctx, rc); err != nil {
94 return rc, gqlerror.List{err}
95 }
96 }
97
98 return rc, nil
99 }
100
101 func (e *Executor) DispatchOperation(
102 ctx context.Context,
103 rc *graphql.OperationContext,
104 ) (graphql.ResponseHandler, context.Context) {
105 ctx = graphql.WithOperationContext(ctx, rc)
106
107 var innerCtx context.Context
108 res := e.ext.operationMiddleware(ctx, func(ctx context.Context) graphql.ResponseHandler {
109 innerCtx = ctx
110
111 tmpResponseContext := graphql.WithResponseContext(ctx, e.errorPresenter, e.recoverFunc)
112 responses := e.es.Exec(tmpResponseContext)
113 if errs := graphql.GetErrors(tmpResponseContext); errs != nil {
114 return graphql.OneShot(&graphql.Response{Errors: errs})
115 }
116
117 return func(ctx context.Context) *graphql.Response {
118 ctx = graphql.WithResponseContext(ctx, e.errorPresenter, e.recoverFunc)
119 resp := e.ext.responseMiddleware(ctx, func(ctx context.Context) *graphql.Response {
120 resp := responses(ctx)
121 if resp == nil {
122 return nil
123 }
124 resp.Errors = append(resp.Errors, graphql.GetErrors(ctx)...)
125 resp.Extensions = graphql.GetExtensions(ctx)
126 return resp
127 })
128 if resp == nil {
129 return nil
130 }
131
132 return resp
133 }
134 })
135
136 return res, innerCtx
137 }
138
139 func (e *Executor) DispatchError(ctx context.Context, list gqlerror.List) *graphql.Response {
140 ctx = graphql.WithResponseContext(ctx, e.errorPresenter, e.recoverFunc)
141 for _, gErr := range list {
142 graphql.AddError(ctx, gErr)
143 }
144
145 resp := e.ext.responseMiddleware(ctx, func(ctx context.Context) *graphql.Response {
146 resp := &graphql.Response{
147 Errors: graphql.GetErrors(ctx),
148 }
149 resp.Extensions = graphql.GetExtensions(ctx)
150 return resp
151 })
152
153 return resp
154 }
155
156 func (e *Executor) PresentRecoveredError(ctx context.Context, err interface{}) error {
157 return e.errorPresenter(ctx, e.recoverFunc(ctx, err))
158 }
159
160 func (e *Executor) SetQueryCache(cache graphql.Cache) {
161 e.queryCache = cache
162 }
163
164 func (e *Executor) SetErrorPresenter(f graphql.ErrorPresenterFunc) {
165 e.errorPresenter = f
166 }
167
168 func (e *Executor) SetRecoverFunc(f graphql.RecoverFunc) {
169 e.recoverFunc = f
170 }
171
172
173
174
175
176
177 func (e *Executor) parseQuery(
178 ctx context.Context,
179 stats *graphql.Stats,
180 query string,
181 ) (*ast.QueryDocument, gqlerror.List) {
182 stats.Parsing.Start = graphql.Now()
183
184 if doc, ok := e.queryCache.Get(ctx, query); ok {
185 now := graphql.Now()
186
187 stats.Parsing.End = now
188 stats.Validation.Start = now
189 return doc.(*ast.QueryDocument), nil
190 }
191
192 doc, err := parser.ParseQuery(&ast.Source{Input: query})
193 if err != nil {
194 gqlErr, ok := err.(*gqlerror.Error)
195 if ok {
196 errcode.Set(gqlErr, errcode.ParseFailed)
197 return nil, gqlerror.List{gqlErr}
198 }
199 }
200 stats.Parsing.End = graphql.Now()
201
202 stats.Validation.Start = graphql.Now()
203
204 if len(doc.Operations) == 0 {
205 err = gqlerror.Errorf("no operation provided")
206 gqlErr, _ := err.(*gqlerror.Error)
207 errcode.Set(err, errcode.ValidationFailed)
208 return nil, gqlerror.List{gqlErr}
209 }
210
211 listErr := validator.Validate(e.es.Schema(), doc)
212 if len(listErr) != 0 {
213 for _, e := range listErr {
214 errcode.Set(e, errcode.ValidationFailed)
215 }
216 return nil, listErr
217 }
218
219 e.queryCache.Add(ctx, query, doc)
220
221 return doc, nil
222 }
223
View as plain text