...

Source file src/github.com/99designs/gqlgen/graphql/executor/executor.go

Documentation: github.com/99designs/gqlgen/graphql/executor

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

View as plain text