Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 8 additions & 1 deletion v2/pkg/astnormalization/astnormalization.go
Original file line number Diff line number Diff line change
Expand Up @@ -150,6 +150,7 @@ type options struct {
removeUnusedVariables bool
removeNotMatchingOperationDefinitions bool
normalizeDefinition bool
ignoreSkipInclude bool
}

type Option func(options *options)
Expand Down Expand Up @@ -190,6 +191,12 @@ func WithNormalizeDefinition() Option {
}
}

func WithIgnoreSkipInclude() Option {
return func(options *options) {
options.ignoreSkipInclude = true
}
}

func (o *OperationNormalizer) setupOperationWalkers() {
o.operationWalkers = make([]walkerStage, 0, 9)

Expand All @@ -209,7 +216,7 @@ func (o *OperationNormalizer) setupOperationWalkers() {

directivesIncludeSkip := astvisitor.NewWalker(8)
preventFragmentCycles(&directivesIncludeSkip)
directiveIncludeSkip(&directivesIncludeSkip)
directiveIncludeSkipKeepNodes(&directivesIncludeSkip, o.options.ignoreSkipInclude)

cleanup := astvisitor.NewWalker(8)
deduplicateFields(&cleanup)
Expand Down
30 changes: 21 additions & 9 deletions v2/pkg/astnormalization/directive_include_skip.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,9 +10,18 @@ import (
"github.com/wundergraph/graphql-go-tools/v2/pkg/lexer/literal"
)

// directiveIncludeSkip registers a visitor to handle @include and @skip directives.
// It deletes nodes that are evaluated as unused by the directives.
func directiveIncludeSkip(walker *astvisitor.Walker) {
directiveIncludeSkipKeepNodes(walker, false)
}

// directiveIncludeSkipKeepNodes registers a visitor to handle @include and @skip directives.
// If keepNodes is true, it unconditionally removes the directives and keeps parent nodes.
func directiveIncludeSkipKeepNodes(walker *astvisitor.Walker, keepNodes bool) {
visitor := directiveIncludeSkipVisitor{
Walker: walker,
Walker: walker,
keepNodes: keepNodes,
}
walker.RegisterEnterDocumentVisitor(&visitor)
walker.RegisterEnterDirectiveVisitor(&visitor)
Expand All @@ -21,6 +30,7 @@ func directiveIncludeSkip(walker *astvisitor.Walker) {
type directiveIncludeSkipVisitor struct {
*astvisitor.Walker
operation, definition *ast.Document
keepNodes bool
}

func (d *directiveIncludeSkipVisitor) EnterDocument(operation, definition *ast.Document) {
Expand Down Expand Up @@ -62,8 +72,8 @@ func (d *directiveIncludeSkipVisitor) handleSkip(ref int) {
default:
return
}
if skip {
d.handleRemoveNode()
if !d.keepNodes && bool(skip) {
d.removeParentNode()
} else {
d.operation.RemoveDirectiveFromNode(d.Ancestors[len(d.Ancestors)-1], ref)
}
Expand Down Expand Up @@ -91,10 +101,10 @@ func (d *directiveIncludeSkipVisitor) handleInclude(ref int) {
default:
return
}
if include {
if d.keepNodes || bool(include) {
d.operation.RemoveDirectiveFromNode(d.Ancestors[len(d.Ancestors)-1], ref)
} else {
d.handleRemoveNode()
d.removeParentNode()
}
}

Expand All @@ -114,17 +124,19 @@ func (d *directiveIncludeSkipVisitor) getVariableValue(name string) (value, vali
return false, false
}

func (d *directiveIncludeSkipVisitor) handleRemoveNode() {
func (d *directiveIncludeSkipVisitor) removeParentNode() {
if len(d.Ancestors) < 2 {
return
}

removed := d.operation.RemoveNodeFromSelectionSetNode(d.Ancestors[len(d.Ancestors)-1], d.Ancestors[len(d.Ancestors)-2])
parent := d.Ancestors[len(d.Ancestors)-1]
grandParent := d.Ancestors[len(d.Ancestors)-2]
removed := d.operation.RemoveNodeFromSelectionSetNode(parent, grandParent)
if !removed {
return
}

if d.Ancestors[len(d.Ancestors)-2].Kind != ast.NodeKindSelectionSet {
if grandParent.Kind != ast.NodeKindSelectionSet {
return
}

Expand All @@ -133,7 +145,7 @@ func (d *directiveIncludeSkipVisitor) handleRemoveNode() {
// So we have to add a __typename selection to the selection set,
// but as this selection was not added by user it should not be added to resolved data

selectionSetRef := d.Ancestors[len(d.Ancestors)-2].Ref
selectionSetRef := grandParent.Ref

if d.operation.SelectionSetIsEmpty(selectionSetRef) {
selectionRef, _ := d.typeNameSelection()
Expand Down
82 changes: 81 additions & 1 deletion v2/pkg/astnormalization/directive_include_skip_test.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,10 @@
package astnormalization

import "testing"
import (
"testing"

"github.com/wundergraph/graphql-go-tools/v2/pkg/astvisitor"
)

func TestDirectiveIncludeVisitor(t *testing.T) {
t.Run("remove static include true on inline fragment", func(t *testing.T) {
Expand Down Expand Up @@ -316,4 +320,80 @@ func TestDirectiveIncludeVisitor(t *testing.T) {
}
}`, `{"yes":true,"no":false}`)
})

t.Run("keepNodes", func(t *testing.T) {
keepNodes := func(walker *astvisitor.Walker) {
directiveIncludeSkipKeepNodes(walker, true)
}

t.Run("skip should keep nodes", func(t *testing.T) {
runWithVariables(t, keepNodes, testDefinition, `
query($yes: Boolean = false, $no: Boolean = true) {
dog {
... @skip(if: $yes) {
includeName: name
}
}
withAlias: dog {
name @skip(if: $no)
}
}`, `
query($yes: Boolean = false, $no: Boolean = true) {
dog {
... {
includeName: name
}
}
withAlias: dog {
name
}
}`, `{"yes":true,"no":false}`)
})
t.Run("include should keep nodes", func(t *testing.T) {
runWithVariables(t, keepNodes, testDefinition, `
query($yes: Boolean = false, $no: Boolean = true) {
dog {
... @include(if: $yes) {
includeName: name
}
}
withAlias: dog {
name @include(if: $no)
}
}`, `
query($yes: Boolean = false, $no: Boolean = true) {
dog {
... {
includeName: name
}
}
withAlias: dog {
name
}
}`, `{"yes":true,"no":false}`)
})
t.Run("include/skip should keep nodes using default values", func(t *testing.T) {
runWithVariables(t, keepNodes, testDefinition, `
query($yes: Boolean = false, $no: Boolean = true) {
dog {
... @include(if: $yes) {
includeName: name
}
}
withAlias: dog {
name @skip(if: $no)
}
}`, `
query($yes: Boolean = false, $no: Boolean = true) {
dog {
... {
includeName: name
}
}
withAlias: dog {
name
}
}`, `{}`)
})
})
}
Loading