Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Added support for @include/@skip directives. #130

Closed
wants to merge 57 commits into from
Closed

Added support for @include/@skip directives. #130

wants to merge 57 commits into from

Conversation

sergeyvlakh
Copy link

Overview

Resolves #126
Added support for builtin @skip/@include directives. They only work with $if argument (!).
This supports syntax with inline variables and default ones.

It is very-very-very WIP PR. I'm really looking into ways of improving this code to actually bring in support for directives!

Default variables case

Query:

query($hero:Boolean = true){
  hero @include (if:$hero) {
    name
  }
}

Result:

{
  "data": {}
}

As expected.

Inline variables case

Query:

{
  hero @include (if:false) {
    name
  }
}

Result:

{
  "data": {}
}

As expected.

Passed variables with default values

Query:

query($hero:Boolean = false){
  hero @include (if:$hero) {
    name
  }
}

Variables:

{
"hero": true
}

Result:

{
  "data": {
    "hero": {
      "name": "R2-D2"
    }
  }
}

As expected.

@vektah
Copy link
Collaborator

vektah commented Jun 12, 2018

Neat!

I think I'm happy to add a hacky solution temporarily until the new parser lands. Once thats in we will need a more generic user pluggable way to define directives, and there will need to be a bit of planning around that to support various use cases (authentication, api fetch resolvers, caching).

graphql/exec.go Outdated
fieldAllowed := true
switch arg.(type) {
case *common.Variable:
{
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm surprised none of the linters fire on this, you don't need curlies here.

graphql/exec.go Outdated
}
default:
// Custom directives are not supported yet
return fieldAllowed
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

probably should return true. Do nothing rather then do something unexpected. Or error.

graphql/exec.go Outdated
func collectFields(doc *query.Document, selSet []query.Selection, satisfies []string, variables map[string]interface{}, visited map[string]bool) []CollectedField {
var groupedFields []CollectedField

oop, err := doc.GetOperation("")
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

😟

Maybe these methods should be refactored to take the request context instead of document so they can get the correct operation

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This comment sill applies, everywhere that calls CollectFields is grabbing the doc out of the RequestContext embedded in execution context, they could pretty easily be updated to do

fields := graphql.CollectFields(ec.RequestContext, sel, implementors)

instead of

fields := graphql.CollectFields(ec.Doc, sel, implementors, ec.Variables)

and then you have access to the right operation instead of assuming it was an anonymous operation.

if variables == nil {
variables = make(map[string]interface{})
}
for _, variable := range operation.Vars {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This seems like a lot of work to do on every fieldset, I think this goes away if we pass context in?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If there will be one specific place which will have both variables and their default values, this will be super good!

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You can do this check inline, the one place you need the value from a variable, this function shouldn't exist.

And it really shouldn't mutate variables, they should be static after the initial validation pass.

@sergeyvlakh
Copy link
Author

Thanks for addressing some comments. I will try to update PR ASAP.

@sergeyvlakh
Copy link
Author

@vektah could you please do review once again? Thanks in advance :}

fieldAllowed := true
switch arg.(type) {
case *common.Variable:
value := arg.Value(variables)
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why can't you pass the operation in here and do the variable lookup only when its actually referenced, instead of every resolver?

populateVariables is a big smell to me, its going to run on every resolver regardless of if there is even a directive.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Will fix ASAP!

graphql/exec.go Outdated
func collectFields(doc *query.Document, selSet []query.Selection, satisfies []string, variables map[string]interface{}, visited map[string]bool) []CollectedField {
var groupedFields []CollectedField

oop, err := doc.GetOperation("")
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This comment sill applies, everywhere that calls CollectFields is grabbing the doc out of the RequestContext embedded in execution context, they could pretty easily be updated to do

fields := graphql.CollectFields(ec.RequestContext, sel, implementors)

instead of

fields := graphql.CollectFields(ec.Doc, sel, implementors, ec.Variables)

and then you have access to the right operation instead of assuming it was an anonymous operation.

@sergeyvlakh
Copy link
Author

@vektah thanks for comments! Will try to fix ASAP!

@sergeyvlakh
Copy link
Author

@vektah
I've moved populateVariables to block only if directive present. Regarding your comment about request ctx I'm not sure. Should I change gotpl to achieve this one? Because we call fields := graphql.CollectFields(ec.Doc, sel, implementors, ec.Variables) in there.
And If it's not necessary, maybe merge this PR and land CollectFieldsWithCtx in another PR ?

@vektah
Copy link
Collaborator

vektah commented Jun 25, 2018

Yeah, the template changes are going to be required. A graphql query can have a few forms:

  • a single, unnamed query, eg { hello { world }.
  • a single, named query, eg query Hello { hello { world } }
  • multiple named queries, and the name of the one you want to run.

The only thing this works for is the first one, in order to get the operation name (you are using "", but thats 100% a bug). All this resolution has already been done though, when the validation happened, and it was put in context so the rest of the runtime knows whats going on.

@sergeyvlakh
Copy link
Author

Actually, as I understood, we are only taking one operation. And doesn't matter if it was named or not according to this code:

if operationName == "" {
		if len(d.Operations) > 1 {
			return nil, fmt.Errorf("more than one operation in query document and no operation name given")
		}
		for _, op := range d.Operations {
			return op, nil // return the one and only operation
		}
	}

@vektah
Copy link
Collaborator

vektah commented Jun 29, 2018

Thats only if operationName == ""

@sergeyvlakh
Copy link
Author

sergeyvlakh commented Jul 2, 2018

@vektah
Updated. I've added OperationName to requestCtx. It wasn't in in previously. Now I'm calling
getOperationName(reqctx.OperationName)
I'm not sure what are those errors in circleci and how do I fix them ?

@vektah
Copy link
Collaborator

vektah commented Jul 13, 2018

The new parser is nearly done now, full directive support should only be a few weeks away.

@sergeyvlakh
Copy link
Author

Great. I'll close second one then. Thanks

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

9 participants