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