Skip to content

Commit

Permalink
vektah#332 Add Path to fields.
Browse files Browse the repository at this point in the history
[WIP] Initial commit.

//todo: check the issue with field being walked twice : from walkOperation and from walkFragment
  • Loading branch information
Nikita Bahliuk committed Nov 26, 2024
1 parent b269ffc commit 65136ba
Show file tree
Hide file tree
Showing 4 changed files with 141 additions and 8 deletions.
2 changes: 2 additions & 0 deletions ast/fragment.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ type FragmentSpread struct {
ObjectDefinition *Definition
Definition *FragmentDefinition

Path Path
Position *Position `dump:"-"`
Comment *CommentGroup
}
Expand All @@ -20,6 +21,7 @@ type InlineFragment struct {
// Require validation
ObjectDefinition *Definition

Path Path
Position *Position `dump:"-"`
Comment *CommentGroup
}
Expand Down
1 change: 1 addition & 0 deletions ast/selection.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ type Field struct {
Directives DirectiveList
SelectionSet SelectionSet
Position *Position `dump:"-"`
Path Path
Comment *CommentGroup

// Require validation
Expand Down
28 changes: 20 additions & 8 deletions validator/walk.go
Original file line number Diff line number Diff line change
Expand Up @@ -121,7 +121,9 @@ func (w *Walker) walkOperation(operation *ast.OperationDefinition) {
}

w.walkDirectives(def, operation.Directives, loc)
w.walkSelectionSet(def, operation.SelectionSet)

var path = ast.Path{}
w.walkSelectionSet(def, operation.SelectionSet, path)

for _, v := range w.Observers.operationVisitor {
v(w, operation)
Expand All @@ -135,7 +137,10 @@ func (w *Walker) walkFragment(it *ast.FragmentDefinition) {
it.Definition = def

w.walkDirectives(def, it.Directives, ast.LocationFragmentDefinition)
w.walkSelectionSet(def, it.SelectionSet)

//todo: this here is a workaround,
//without it Path info is erased due to double walk on same field from the fragment
w.walkSelectionSet(def, it.SelectionSet, nil)

for _, v := range w.Observers.fragment {
v(w, it)
Expand Down Expand Up @@ -214,13 +219,13 @@ func (w *Walker) walkArgument(argDef *ast.ArgumentDefinition, arg *ast.Argument)
w.walkValue(arg.Value)
}

func (w *Walker) walkSelectionSet(parentDef *ast.Definition, it ast.SelectionSet) {
func (w *Walker) walkSelectionSet(parentDef *ast.Definition, it ast.SelectionSet, path ast.Path) {
for _, child := range it {
w.walkSelection(parentDef, child)
w.walkSelection(parentDef, child, path)
}
}

func (w *Walker) walkSelection(parentDef *ast.Definition, it ast.Selection) {
func (w *Walker) walkSelection(parentDef *ast.Definition, it ast.Selection, path ast.Path) {
switch it := it.(type) {
case *ast.Field:
var def *ast.FieldDefinition
Expand All @@ -233,6 +238,13 @@ func (w *Walker) walkSelection(parentDef *ast.Definition, it ast.Selection) {
def = parentDef.Fields.ForName(it.Name)
}

// when we walk fragments there is no need to set paths (they are already set and walkFragment() passes nil as path)
if path != nil {
fieldPath := path
fieldPath = append(fieldPath, ast.PathName(it.Alias))
it.Path = fieldPath
}

it.Definition = def
it.ObjectDefinition = parentDef

Expand All @@ -251,7 +263,7 @@ func (w *Walker) walkSelection(parentDef *ast.Definition, it ast.Selection) {
}

w.walkDirectives(nextParentDef, it.Directives, ast.LocationField)
w.walkSelectionSet(nextParentDef, it.SelectionSet)
w.walkSelectionSet(nextParentDef, it.SelectionSet, it.Path)

for _, v := range w.Observers.field {
v(w, it)
Expand All @@ -266,7 +278,7 @@ func (w *Walker) walkSelection(parentDef *ast.Definition, it ast.Selection) {
}

w.walkDirectives(nextParentDef, it.Directives, ast.LocationInlineFragment)
w.walkSelectionSet(nextParentDef, it.SelectionSet)
w.walkSelectionSet(nextParentDef, it.SelectionSet, path)

for _, v := range w.Observers.inlineFragment {
v(w, it)
Expand All @@ -287,7 +299,7 @@ func (w *Walker) walkSelection(parentDef *ast.Definition, it ast.Selection) {
if def != nil && !w.validatedFragmentSpreads[def.Name] {
// prevent infinite recursion
w.validatedFragmentSpreads[def.Name] = true
w.walkSelectionSet(nextParentDef, def.SelectionSet)
w.walkSelectionSet(nextParentDef, def.SelectionSet, path)
}

for _, v := range w.Observers.fragmentSpread {
Expand Down
118 changes: 118 additions & 0 deletions validator/walk_test.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package validator

import (
"fmt"
"testing"

"github.com/stretchr/testify/require"
Expand Down Expand Up @@ -50,3 +51,120 @@ func TestWalkInlineFragment(t *testing.T) {

require.True(t, called)
}

func TestFieldPath(t *testing.T) {
topFieldName := "topLevelField"
nestedFieldName := "nestedField"
deeplyNestedFieldName := "deeplyNestedField"
deeplyNestedFieldNameAlias := "aliasDeeplyNestedField"

schemaStr := fmt.Sprintf(`
type Query {
%s: TopLevelField
}
type TopLevelField {
%s: NestedField
}
type NestedField {
%s: String
}
schema { query: Query }`,
topFieldName,
nestedFieldName,
deeplyNestedFieldName,
)

schema, err := LoadSchema(Prelude, &ast.Source{Input: schemaStr})
require.NoError(t, err)

testCases := []struct {
name string
queryStr string
}{
{
"simple query",
fmt.Sprintf(`
{
%s {
%s {
%s: %s
}
}
}`,
topFieldName,
nestedFieldName,
deeplyNestedFieldNameAlias,
deeplyNestedFieldName,
),
},
{
"query with fragment",
fmt.Sprintf(`
{
%s {
%s {
...NestedFieldFragment
}
}
}
fragment NestedFieldFragment on NestedField {
%s: %s
}
`,
topFieldName,
nestedFieldName,
deeplyNestedFieldNameAlias,
deeplyNestedFieldName,
),
},
{
"query with inline-fragment",
fmt.Sprintf(`
{
%s {
%s {
...on NestedField{
%s: %s
}
}
}
}
`,
topFieldName,
nestedFieldName,
deeplyNestedFieldNameAlias,
deeplyNestedFieldName,
),
},
}

for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
query, err := parser.ParseQuery(&ast.Source{Input: tc.queryStr})
require.NoError(t, err)
called := 0
observers := &Events{}

observers.OnField(func(walker *Walker, field *ast.Field) {
called++
fieldPath := field.Path.String()
switch field.Name {
case topFieldName:
require.Equal(t, topFieldName, fieldPath)
case nestedFieldName:
require.Equal(t, fmt.Sprintf("%s.%s", topFieldName, nestedFieldName), fieldPath)
case deeplyNestedFieldName:
require.Equal(t, fmt.Sprintf("%s.%s.%s", topFieldName, nestedFieldName, deeplyNestedFieldNameAlias), fieldPath)
default:
require.Fail(t, "unexpected field name: "+field.Name)
}
})

Walk(schema, query, observers)
require.Equal(t, 3, called)
})
}
}

0 comments on commit 65136ba

Please sign in to comment.