Skip to content

Commit

Permalink
Merge pull request #117 from vektah/feat-path
Browse files Browse the repository at this point in the history
introduce ast.Path type
  • Loading branch information
vvakame authored Feb 9, 2020
2 parents c06d8e0 + fcdc574 commit a7a59ec
Show file tree
Hide file tree
Showing 6 changed files with 178 additions and 27 deletions.
67 changes: 67 additions & 0 deletions ast/path.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
package ast

import (
"bytes"
"encoding/json"
"fmt"
)

var _ json.Unmarshaler = (*Path)(nil)

type Path []PathElement

type PathElement interface {
isPathElement()
}

var _ PathElement = PathIndex(0)
var _ PathElement = PathName("")

func (path Path) String() string {
var str bytes.Buffer
for i, v := range path {
switch v := v.(type) {
case PathIndex:
str.WriteString(fmt.Sprintf("[%d]", v))
case PathName:
if i != 0 {
str.WriteByte('.')
}
str.WriteString(string(v))
default:
panic(fmt.Sprintf("unknown type: %T", v))
}
}
return str.String()
}

func (path *Path) UnmarshalJSON(b []byte) error {
var vs []interface{}
err := json.Unmarshal(b, &vs)
if err != nil {
return err
}

*path = make([]PathElement, 0, len(vs))
for _, v := range vs {
switch v := v.(type) {
case string:
*path = append(*path, PathName(v))
case int:
*path = append(*path, PathIndex(v))
case float64:
*path = append(*path, PathIndex(int(v)))
default:
return fmt.Errorf("unknown path element type: %T", v)
}
}
return nil
}

type PathIndex int

func (_ PathIndex) isPathElement() {}

type PathName string

func (_ PathName) isPathElement() {}
96 changes: 96 additions & 0 deletions ast/path_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
package ast

import (
"encoding/json"
"testing"

"github.com/stretchr/testify/require"
)

func TestPath_String(t *testing.T) {
type Spec struct {
Value Path
Expected string
}
specs := []*Spec{
{
Value: Path{PathName("a"), PathIndex(2), PathName("c")},
Expected: "a[2].c",
},
{
Value: Path{},
Expected: ``,
},
{
Value: Path{PathIndex(1), PathName("b")},
Expected: `[1].b`,
},
}

for _, spec := range specs {
t.Run(spec.Value.String(), func(t *testing.T) {
require.Equal(t, spec.Expected, spec.Value.String())
})
}
}

func TestPath_MarshalJSON(t *testing.T) {
type Spec struct {
Value Path
Expected string
}
specs := []*Spec{
{
Value: Path{PathName("a"), PathIndex(2), PathName("c")},
Expected: `["a",2,"c"]`,
},
{
Value: Path{},
Expected: `[]`,
},
{
Value: Path{PathIndex(1), PathName("b")},
Expected: `[1,"b"]`,
},
}

for _, spec := range specs {
t.Run(spec.Value.String(), func(t *testing.T) {
b, err := json.Marshal(spec.Value)
require.Nil(t, err)

require.Equal(t, spec.Expected, string(b))
})
}
}

func TestPath_UnmarshalJSON(t *testing.T) {
type Spec struct {
Value string
Expected Path
}
specs := []*Spec{
{
Value: `["a",2,"c"]`,
Expected: Path{PathName("a"), PathIndex(2), PathName("c")},
},
{
Value: `[]`,
Expected: Path{},
},
{
Value: `[1,"b"]`,
Expected: Path{PathIndex(1), PathName("b")},
},
}

for _, spec := range specs {
t.Run(spec.Value, func(t *testing.T) {
var path Path
err := json.Unmarshal([]byte(spec.Value), &path)
require.Nil(t, err)

require.Equal(t, spec.Expected, path)
})
}
}
6 changes: 3 additions & 3 deletions ast/source.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,13 @@ package ast
// Source covers a single *.graphql file
type Source struct {
// Name is the filename of the source
Name string
Name string
// Input is the actual contents of the source file
Input string
Input string
// BuiltIn indicate whether the source is a part of the specification
BuiltIn bool
}

type Position struct {
Start int // The starting position, in runes, of this token in the input.
End int // The end position, in runes, of this token in the input.
Expand Down
21 changes: 4 additions & 17 deletions gqlerror/error.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ import (
// Error is the standard graphql error type described in https://facebook.github.io/graphql/draft/#sec-Errors
type Error struct {
Message string `json:"message"`
Path []interface{} `json:"path,omitempty"`
Path ast.Path `json:"path,omitempty"`
Locations []Location `json:"locations,omitempty"`
Extensions map[string]interface{} `json:"extensions,omitempty"`
Rule string `json:"-"`
Expand Down Expand Up @@ -63,20 +63,7 @@ func (err *Error) Error() string {
}

func (err Error) pathString() string {
var str bytes.Buffer
for i, v := range err.Path {

switch v := v.(type) {
case int, int64:
str.WriteString(fmt.Sprintf("[%d]", v))
default:
if i != 0 {
str.WriteByte('.')
}
str.WriteString(fmt.Sprint(v))
}
}
return str.String()
return err.Path.String()
}

func (errs List) Error() string {
Expand All @@ -88,7 +75,7 @@ func (errs List) Error() string {
return buf.String()
}

func WrapPath(path []interface{}, err error) *Error {
func WrapPath(path ast.Path, err error) *Error {
return &Error{
Message: err.Error(),
Path: path,
Expand All @@ -101,7 +88,7 @@ func Errorf(message string, args ...interface{}) *Error {
}
}

func ErrorPathf(path []interface{}, message string, args ...interface{}) *Error {
func ErrorPathf(path ast.Path, message string, args ...interface{}) *Error {
return &Error{
Message: fmt.Sprintf(message, args...),
Path: path,
Expand Down
3 changes: 2 additions & 1 deletion gqlerror/error_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import (
"testing"

"github.com/stretchr/testify/require"
"github.com/vektah/gqlparser/ast"
)

func TestErrorFormatting(t *testing.T) {
Expand All @@ -22,7 +23,7 @@ func TestErrorFormatting(t *testing.T) {
})

t.Run("with path", func(t *testing.T) {
err := ErrorPathf([]interface{}{"a", 1, "b"}, "kabloom")
err := ErrorPathf(ast.Path{ast.PathName("a"), ast.PathIndex(1), ast.PathName("b")}, "kabloom")

require.Equal(t, `input: a[1].b kabloom`, err.Error())
})
Expand Down
12 changes: 6 additions & 6 deletions validator/vars.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,12 +17,12 @@ func VariableValues(schema *ast.Schema, op *ast.OperationDefinition, variables m
coercedVars := map[string]interface{}{}

validator := varValidator{
path: []interface{}{"variable"},
path: ast.Path{ast.PathName("variable")},
schema: schema,
}

for _, v := range op.VariableDefinitions {
validator.path = append(validator.path, v.Variable)
validator.path = append(validator.path, ast.PathName(v.Variable))

if !v.Definition.IsInputType() {
return nil, gqlerror.ErrorPathf(validator.path, "must an input type")
Expand Down Expand Up @@ -69,7 +69,7 @@ func VariableValues(schema *ast.Schema, op *ast.OperationDefinition, variables m
}

type varValidator struct {
path []interface{}
path ast.Path
schema *ast.Schema
}

Expand All @@ -87,7 +87,7 @@ func (v *varValidator) validateVarType(typ *ast.Type, val reflect.Value) *gqlerr

for i := 0; i < val.Len(); i++ {
resetPath()
v.path = append(v.path, i)
v.path = append(v.path, ast.PathIndex(i))
field := val.Index(i)

if field.Kind() == reflect.Ptr || field.Kind() == reflect.Interface {
Expand Down Expand Up @@ -171,7 +171,7 @@ func (v *varValidator) validateVarType(typ *ast.Type, val reflect.Value) *gqlerr
val.MapIndex(name)
fieldDef := def.Fields.ForName(name.String())
resetPath()
v.path = append(v.path, name.String())
v.path = append(v.path, ast.PathName(name.String()))

if fieldDef == nil {
return gqlerror.ErrorPathf(v.path, "unknown field")
Expand All @@ -180,7 +180,7 @@ func (v *varValidator) validateVarType(typ *ast.Type, val reflect.Value) *gqlerr

for _, fieldDef := range def.Fields {
resetPath()
v.path = append(v.path, fieldDef.Name)
v.path = append(v.path, ast.PathName(fieldDef.Name))

field := val.MapIndex(reflect.ValueOf(fieldDef.Name))
if !field.IsValid() {
Expand Down

0 comments on commit a7a59ec

Please sign in to comment.