Skip to content

Commit

Permalink
PR: Rework the parsing logic of directives out of each operation spec…
Browse files Browse the repository at this point in the history
…ific function and in one place.
  • Loading branch information
shahzadlone committed Jan 17, 2023
1 parent ab45e89 commit ebc32c7
Show file tree
Hide file tree
Showing 3 changed files with 120 additions and 43 deletions.
2 changes: 0 additions & 2 deletions query/graphql/parser/mutation.go
Original file line number Diff line number Diff line change
Expand Up @@ -39,8 +39,6 @@ func parseMutationOperationDefinition(
Selections: make([]request.Selection, len(def.SelectionSet.Selections)),
}

qdef.IsExplain = parseExplainDirective(def.Directives)

for i, selection := range def.SelectionSet.Selections {
switch node := selection.(type) {
case *ast.Field:
Expand Down
159 changes: 120 additions & 39 deletions query/graphql/parser/query.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,9 @@ import (
"github.com/sourcenetwork/immutable"

"github.com/sourcenetwork/defradb/client"
"github.com/sourcenetwork/defradb/errors"
schemaTypes "github.com/sourcenetwork/defradb/query/graphql/schema/types"

"github.com/sourcenetwork/defradb/client/request"
)

Expand All @@ -28,74 +31,152 @@ func ParseQuery(schema gql.Schema, doc *ast.Document) (*request.Request, []error
if doc == nil {
return nil, []error{client.NewErrUninitializeProperty("parseQuery", "doc")}
}

r := &request.Request{
Queries: make([]*request.OperationDefinition, 0),
Mutations: make([]*request.OperationDefinition, 0),
Subscription: make([]*request.OperationDefinition, 0),
}

for _, def := range doc.Definitions {
switch node := def.(type) {
case *ast.OperationDefinition:
switch node.Operation {
case "query":
// parse query operation definition.
qdef, err := parseQueryOperationDefinition(schema, node)
if err != nil {
return nil, err
}
r.Queries = append(r.Queries, qdef)
case "mutation":
// parse mutation operation definition.
mdef, err := parseMutationOperationDefinition(schema, node)
if err != nil {
return nil, []error{err}
}
r.Mutations = append(r.Mutations, mdef)
case "subscription":
// parse subscription operation definition.
sdef, err := parseSubscriptionOperationDefinition(schema, node)
if err != nil {
return nil, []error{err}
}
r.Subscription = append(r.Subscription, sdef)
default:
return nil, []error{ErrUnknownGQLOperation}
astOpDef, isOpDef := def.(*ast.OperationDefinition)
if !isOpDef {
continue
}

switch astOpDef.Operation {
case "query":
parsedQueryOpDef, errs := parseQueryOperationDefinition(schema, astOpDef)
if errs != nil {
return nil, errs
}

parsedDirectives, err := parseDirectives(astOpDef.Directives)
if errs != nil {
return nil, []error{err}
}
parsedQueryOpDef.Directives = parsedDirectives

r.Queries = append(r.Queries, parsedQueryOpDef)

case "mutation":
parsedMutationOpDef, err := parseMutationOperationDefinition(schema, astOpDef)
if err != nil {
return nil, []error{err}
}

parsedDirectives, err := parseDirectives(astOpDef.Directives)
if err != nil {
return nil, []error{err}
}
parsedMutationOpDef.Directives = parsedDirectives

r.Mutations = append(r.Mutations, parsedMutationOpDef)

case "subscription":
parsedSubscriptionOpDef, err := parseSubscriptionOperationDefinition(schema, astOpDef)
if err != nil {
return nil, []error{err}
}

parsedDirectives, err := parseDirectives(astOpDef.Directives)
if err != nil {
return nil, []error{err}
}
parsedSubscriptionOpDef.Directives = parsedDirectives

r.Subscription = append(r.Subscription, parsedSubscriptionOpDef)

default:
return nil, []error{ErrUnknownGQLOperation}
}
}

return r, nil
}

// parseExplainDirective returns true if we parsed / detected the explain directive label
// in this ast, and false otherwise.
func parseExplainDirective(directives []*ast.Directive) bool {
// Iterate through all directives and ensure that the directive is at there.
// parseDirectives returns all directives that were found if parsing and validation succeeds,
// otherwise returns the first error that is encountered.
func parseDirectives(astDirectives []*ast.Directive) (request.Directives, error) {
// Set the default states of the directives if they aren't found and no error(s) occur.
explainDirective := immutable.None[request.ExplainType]()

// Iterate through all directives and ensure that the directive we find are validated.
// - Note: the location we don't need to worry about as the schema takes care of it, as when
// request is made there will be a syntax error for directive usage at the wrong location,
// unless we add another directive named `@explain` at another location (which we should not).
for _, directive := range directives {
// The arguments pased to the directive are at `directive.Arguments`.
if directive.Name.Value == request.ExplainLabel {
return true
// unless we add another directive with the same name, for example `@explain` is added
// at another location (which we must avoid).
for _, astDirective := range astDirectives {
if astDirective == nil {
return request.Directives{}, errors.New("found a nil directive in the AST")
}

if astDirective.Name == nil || astDirective.Name.Value == "" {
return request.Directives{}, errors.New("found a directive with no name in the AST")
}

if astDirective.Name.Value == request.ExplainLabel {
// Explain directive found, lets parse and validate the directive.
parsedExplainDirctive, err := parseExplainDirective(astDirective)
if err != nil {
return request.Directives{}, err
}
explainDirective = parsedExplainDirctive
}
}

return false
return request.Directives{
ExplainType: explainDirective,
}, nil
}

// parseExplainDirective parses the explain directive AST and returns an error if the parsing or
// validation goes wrong, otherwise returns the parsed explain type information.
func parseExplainDirective(astDirective *ast.Directive) (immutable.Option[request.ExplainType], error) {
if len(astDirective.Arguments) == 0 {
return immutable.Some(request.SimpleExplain), nil
}

if len(astDirective.Arguments) != 1 {
return immutable.None[request.ExplainType](),
errors.New("invalid number of arguments to an explain request")
}

arg := astDirective.Arguments[0]
if arg.Name.Value != schemaTypes.ExplainArgNameType {
return immutable.None[request.ExplainType](),
errors.New("invalid explain request argument")
}

switch arg.Value.GetValue() {
case schemaTypes.ExplainArgSimple:
return immutable.Some(request.SimpleExplain), nil

case schemaTypes.ExplainArgDebug:
return immutable.Some(request.DebugExplain), nil

case schemaTypes.ExplainArgExecute:
return immutable.Some(request.ExecuteExplain), nil

case schemaTypes.ExplainArgPredict:
return immutable.Some(request.PredictExplain), nil

default:
return immutable.None[request.ExplainType](),
errors.New("invalid / unknown explain type")
}
}

// parseQueryOperationDefinition parses the individual GraphQL
// 'query' operations, which there may be multiple of.
func parseQueryOperationDefinition(
schema gql.Schema,
def *ast.OperationDefinition) (*request.OperationDefinition, []error) {
def *ast.OperationDefinition,
) (*request.OperationDefinition, []error) {
qdef := &request.OperationDefinition{
Selections: make([]request.Selection, len(def.SelectionSet.Selections)),
}

qdef.IsExplain = parseExplainDirective(def.Directives)

for i, selection := range def.SelectionSet.Selections {
var parsedSelection request.Selection
switch node := selection.(type) {
Expand Down
2 changes: 0 additions & 2 deletions query/graphql/parser/subscription.go
Original file line number Diff line number Diff line change
Expand Up @@ -27,8 +27,6 @@ func parseSubscriptionOperationDefinition(
Selections: make([]request.Selection, len(def.SelectionSet.Selections)),
}

sdef.IsExplain = parseExplainDirective(def.Directives)

for i, selection := range def.SelectionSet.Selections {
switch node := selection.(type) {
case *ast.Field:
Expand Down

0 comments on commit ebc32c7

Please sign in to comment.