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