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