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  }