github.com/maeglindeveloper/gqlgen@v0.13.1-0.20210413081235-57808b12a0a0/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 Stats: graphql.Stats{ 46 Read: params.ReadTime, 47 OperationStart: graphql.GetStartTime(ctx), 48 }, 49 } 50 ctx = graphql.WithOperationContext(ctx, rc) 51 52 for _, p := range e.ext.operationParameterMutators { 53 if err := p.MutateOperationParameters(ctx, params); err != nil { 54 return rc, gqlerror.List{err} 55 } 56 } 57 58 rc.RawQuery = params.Query 59 rc.OperationName = params.OperationName 60 61 var listErr gqlerror.List 62 rc.Doc, listErr = e.parseQuery(ctx, &rc.Stats, params.Query) 63 if len(listErr) != 0 { 64 return rc, listErr 65 } 66 67 rc.Operation = rc.Doc.Operations.ForName(params.OperationName) 68 if rc.Operation == nil { 69 return rc, gqlerror.List{gqlerror.Errorf("operation %s not found", params.OperationName)} 70 } 71 72 var err *gqlerror.Error 73 rc.Variables, err = validator.VariableValues(e.es.Schema(), rc.Operation, params.Variables) 74 if err != nil { 75 errcode.Set(err, errcode.ValidationFailed) 76 return rc, gqlerror.List{err} 77 } 78 rc.Stats.Validation.End = graphql.Now() 79 80 for _, p := range e.ext.operationContextMutators { 81 if err := p.MutateOperationContext(ctx, rc); err != nil { 82 return rc, gqlerror.List{err} 83 } 84 } 85 86 return rc, nil 87 } 88 89 func (e *Executor) DispatchOperation(ctx context.Context, rc *graphql.OperationContext) (graphql.ResponseHandler, context.Context) { 90 ctx = graphql.WithOperationContext(ctx, rc) 91 92 var innerCtx context.Context 93 res := e.ext.operationMiddleware(ctx, func(ctx context.Context) graphql.ResponseHandler { 94 innerCtx = ctx 95 96 tmpResponseContext := graphql.WithResponseContext(ctx, e.errorPresenter, e.recoverFunc) 97 responses := e.es.Exec(tmpResponseContext) 98 if errs := graphql.GetErrors(tmpResponseContext); errs != nil { 99 return graphql.OneShot(&graphql.Response{Errors: errs}) 100 } 101 102 return func(ctx context.Context) *graphql.Response { 103 ctx = graphql.WithResponseContext(ctx, e.errorPresenter, e.recoverFunc) 104 resp := e.ext.responseMiddleware(ctx, func(ctx context.Context) *graphql.Response { 105 resp := responses(ctx) 106 if resp == nil { 107 return nil 108 } 109 resp.Errors = append(resp.Errors, graphql.GetErrors(ctx)...) 110 resp.Extensions = graphql.GetExtensions(ctx) 111 return resp 112 }) 113 if resp == nil { 114 return nil 115 } 116 117 return resp 118 } 119 }) 120 121 return res, innerCtx 122 } 123 124 func (e *Executor) DispatchError(ctx context.Context, list gqlerror.List) *graphql.Response { 125 ctx = graphql.WithResponseContext(ctx, e.errorPresenter, e.recoverFunc) 126 for _, gErr := range list { 127 graphql.AddError(ctx, gErr) 128 } 129 130 resp := e.ext.responseMiddleware(ctx, func(ctx context.Context) *graphql.Response { 131 resp := &graphql.Response{ 132 Errors: list, 133 } 134 resp.Extensions = graphql.GetExtensions(ctx) 135 return resp 136 }) 137 138 return resp 139 } 140 141 func (e *Executor) PresentRecoveredError(ctx context.Context, err interface{}) *gqlerror.Error { 142 return e.errorPresenter(ctx, e.recoverFunc(ctx, err)) 143 } 144 145 func (e *Executor) SetQueryCache(cache graphql.Cache) { 146 e.queryCache = cache 147 } 148 149 func (e *Executor) SetErrorPresenter(f graphql.ErrorPresenterFunc) { 150 e.errorPresenter = f 151 } 152 153 func (e *Executor) SetRecoverFunc(f graphql.RecoverFunc) { 154 e.recoverFunc = f 155 } 156 157 // parseQuery decodes the incoming query and validates it, pulling from cache if present. 158 // 159 // NOTE: This should NOT look at variables, they will change per request. It should only parse and validate 160 // the raw query string. 161 func (e *Executor) parseQuery(ctx context.Context, stats *graphql.Stats, query string) (*ast.QueryDocument, gqlerror.List) { 162 stats.Parsing.Start = graphql.Now() 163 164 if doc, ok := e.queryCache.Get(ctx, query); ok { 165 now := graphql.Now() 166 167 stats.Parsing.End = now 168 stats.Validation.Start = now 169 return doc.(*ast.QueryDocument), nil 170 } 171 172 doc, err := parser.ParseQuery(&ast.Source{Input: query}) 173 if err != nil { 174 errcode.Set(err, errcode.ParseFailed) 175 return nil, gqlerror.List{err} 176 } 177 stats.Parsing.End = graphql.Now() 178 179 stats.Validation.Start = graphql.Now() 180 listErr := validator.Validate(e.es.Schema(), doc) 181 if len(listErr) != 0 { 182 for _, e := range listErr { 183 errcode.Set(e, errcode.ValidationFailed) 184 } 185 return nil, listErr 186 } 187 188 e.queryCache.Add(ctx, query, doc) 189 190 return doc, nil 191 }