github.com/99designs/gqlgen@v0.17.45/graphql/executor/executor.go (about) 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 // Executor executes graphql queries against a schema. 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 // New creates a new Executor with the given schema, and a default error and 29 // recovery callbacks, and no query cache or extensions. 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 // parseQuery decodes the incoming query and validates it, pulling from cache if present. 173 // 174 // NOTE: This should NOT look at variables, they will change per request. It should only parse and 175 // validate 176 // the raw query string. 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 }