Skip to content

Commit

Permalink
Merge pull request #71 from vektah/feat-empty-validate
Browse files Browse the repository at this point in the history
validate Object/Input Object/Enum/Interface must define one or more f…
  • Loading branch information
vektah committed Nov 13, 2018
2 parents 07406bb + 9bba2f2 commit 6f09700
Show file tree
Hide file tree
Showing 6 changed files with 228 additions and 32 deletions.
20 changes: 20 additions & 0 deletions parser/parser.go
Original file line number Diff line number Diff line change
Expand Up @@ -110,3 +110,23 @@ func (p *parser) many(start lexer.Type, end lexer.Type, cb func()) {
}
p.next()
}

func (p *parser) some(start lexer.Type, end lexer.Type, cb func()) {
hasDef := p.skip(start)
if !hasDef {
return
}

called := false
for p.peek().Kind != end && p.err == nil {
called = true
cb()
}

if !called {
p.error(p.peek(), "expected at least one definition, found %s", p.peek().Kind.String())
return
}

p.next()
}
106 changes: 80 additions & 26 deletions parser/parser_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -29,42 +29,96 @@ func TestParserUtils(t *testing.T) {
require.Nil(t, p.err)
})

t.Run("test many can read array", func(t *testing.T) {
p := newParser("[a b c d]")
t.Run("test many", func(t *testing.T) {
t.Run("can read array", func(t *testing.T) {
p := newParser("[a b c d]")

var arr []string
p.many(lexer.BracketL, lexer.BracketR, func() {
arr = append(arr, p.next().Value)
})
require.Nil(t, p.err)
require.Equal(t, []string{"a", "b", "c", "d"}, arr)

require.Equal(t, lexer.EOF, p.peek().Kind)
require.Nil(t, p.err)
})

t.Run("return if open is not found", func(t *testing.T) {
p := newParser("turtles are happy")

var arr []string
p.many(lexer.BracketL, lexer.BracketR, func() {
arr = append(arr, p.next().Value)
p.many(lexer.BracketL, lexer.BracketR, func() {
t.Error("cb should not be called")
})
require.Nil(t, p.err)
require.Equal(t, "turtles", p.next().Value)
})
require.Nil(t, p.err)
require.Equal(t, []string{"a", "b", "c", "d"}, arr)

require.Equal(t, lexer.EOF, p.peek().Kind)
require.Nil(t, p.err)
t.Run("will stop on error", func(t *testing.T) {
p := newParser("[a b c d]")

var arr []string
p.many(lexer.BracketL, lexer.BracketR, func() {
arr = append(arr, p.next().Value)
if len(arr) == 2 {
p.error(p.peek(), "boom")
}
})
require.EqualError(t, p.err, "input.graphql:1: boom")
require.Equal(t, []string{"a", "b"}, arr)
})
})

t.Run("test many return if open is not found", func(t *testing.T) {
p := newParser("turtles are happy")
t.Run("test some", func(t *testing.T) {
t.Run("can read array", func(t *testing.T) {
p := newParser("[a b c d]")

var arr []string
p.some(lexer.BracketL, lexer.BracketR, func() {
arr = append(arr, p.next().Value)
})
require.Nil(t, p.err)
require.Equal(t, []string{"a", "b", "c", "d"}, arr)

p.many(lexer.BracketL, lexer.BracketR, func() {
t.Error("cb should not be called")
require.Equal(t, lexer.EOF, p.peek().Kind)
require.Nil(t, p.err)
})
require.Nil(t, p.err)
require.Equal(t, "turtles", p.next().Value)
})

t.Run("test many will stop on error", func(t *testing.T) {
p := newParser("[a b c d]")
t.Run("can't read empty array", func(t *testing.T) {
p := newParser("[]")

var arr []string
p.some(lexer.BracketL, lexer.BracketR, func() {
arr = append(arr, p.next().Value)
})
require.EqualError(t, p.err, "input.graphql:1: expected at least one definition, found ]")
require.Equal(t, []string(nil), arr)
require.NotEqual(t, lexer.EOF, p.peek().Kind)
})

t.Run("return if open is not found", func(t *testing.T) {
p := newParser("turtles are happy")

p.some(lexer.BracketL, lexer.BracketR, func() {
t.Error("cb should not be called")
})
require.Nil(t, p.err)
require.Equal(t, "turtles", p.next().Value)
})

var arr []string
p.many(lexer.BracketL, lexer.BracketR, func() {
arr = append(arr, p.next().Value)
if len(arr) == 2 {
p.error(p.peek(), "boom")
}
t.Run("will stop on error", func(t *testing.T) {
p := newParser("[a b c d]")

var arr []string
p.some(lexer.BracketL, lexer.BracketR, func() {
arr = append(arr, p.next().Value)
if len(arr) == 2 {
p.error(p.peek(), "boom")
}
})
require.EqualError(t, p.err, "input.graphql:1: boom")
require.Equal(t, []string{"a", "b"}, arr)
})
require.EqualError(t, p.err, "input.graphql:1: boom")
require.Equal(t, []string{"a", "b"}, arr)
})

t.Run("test errors", func(t *testing.T) {
Expand Down
12 changes: 6 additions & 6 deletions parser/schema.go
Original file line number Diff line number Diff line change
Expand Up @@ -108,7 +108,7 @@ func (p *parser) parseSchemaDefinition(description string) *SchemaDefinition {
def.Description = description
def.Directives = p.parseDirectives(true)

p.many(lexer.BraceL, lexer.BraceR, func() {
p.some(lexer.BraceL, lexer.BraceR, func() {
def.OperationTypes = append(def.OperationTypes, p.parseOperationTypeDefinition())
})
return &def
Expand Down Expand Up @@ -166,7 +166,7 @@ func (p *parser) parseImplementsInterfaces() []string {

func (p *parser) parseFieldsDefinition() FieldList {
var defs FieldList
p.many(lexer.BraceL, lexer.BraceR, func() {
p.some(lexer.BraceL, lexer.BraceR, func() {
defs = append(defs, p.parseFieldDefinition())
})
return defs
Expand All @@ -187,7 +187,7 @@ func (p *parser) parseFieldDefinition() *FieldDefinition {

func (p *parser) parseArgumentDefs() ArgumentDefinitionList {
var args ArgumentDefinitionList
p.many(lexer.ParenL, lexer.ParenR, func() {
p.some(lexer.ParenL, lexer.ParenR, func() {
args = append(args, p.parseArgumentDef())
})
return args
Expand Down Expand Up @@ -276,7 +276,7 @@ func (p *parser) parseEnumTypeDefinition(description string) *Definition {

func (p *parser) parseEnumValuesDefinition() EnumValueList {
var values EnumValueList
p.many(lexer.BraceL, lexer.BraceR, func() {
p.some(lexer.BraceL, lexer.BraceR, func() {
values = append(values, p.parseEnumValueDefinition())
})
return values
Expand Down Expand Up @@ -306,7 +306,7 @@ func (p *parser) parseInputObjectTypeDefinition(description string) *Definition

func (p *parser) parseInputFieldsDefinition() FieldList {
var values FieldList
p.many(lexer.BraceL, lexer.BraceR, func() {
p.some(lexer.BraceL, lexer.BraceR, func() {
values = append(values, p.parseInputValueDef())
})
return values
Expand Down Expand Up @@ -341,7 +341,7 @@ func (p *parser) parseSchemaExtension() *SchemaDefinition {
var def SchemaDefinition
def.Position = p.peekPos()
def.Directives = p.parseDirectives(true)
p.many(lexer.BraceL, lexer.BraceR, func() {
p.some(lexer.BraceL, lexer.BraceR, func() {
def.OperationTypes = append(def.OperationTypes, p.parseOperationTypeDefinition())
})
if len(def.Directives) == 0 && len(def.OperationTypes) == 0 {
Expand Down
24 changes: 24 additions & 0 deletions parser/schema_test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -136,6 +136,12 @@ object types:
Name: "argTwo"
Type: Int
Type: String
- name: must define one or more fields
input: |
type Hello {}
error:
message: "expected at least one definition, found }"
locations: [{ line: 1, column: 13 }]

type extensions:
- name: Object extension
Expand Down Expand Up @@ -378,6 +384,12 @@ enums:
Name: "WO"
- <EnumValueDefinition>
Name: "RLD"
- name: must define one or more unique enum values
input: |
enum Hello {}
error:
message: "expected at least one definition, found }"
locations: [{ line: 1, column: 13 }]

interface:
- name: simple
Expand All @@ -395,6 +407,12 @@ interface:
- <FieldDefinition>
Name: "world"
Type: String
- name: must define one or more fields
input: |
interface Hello {}
error:
message: "expected at least one definition, found }"
locations: [{ line: 1, column: 18 }]

unions:
- name: simple
Expand Down Expand Up @@ -485,6 +503,12 @@ input object:
error:
message: "Expected :, found ("
locations: [{ line: 2, column: 8 }]
- name: must define one or more input fields
input: |
input Hello {}
error:
message: "expected at least one definition, found }"
locations: [{ line: 1, column: 14 }]

directives:
- name: simple
Expand Down
15 changes: 15 additions & 0 deletions validator/schema.go
Original file line number Diff line number Diff line change
Expand Up @@ -184,6 +184,21 @@ func validateDefinition(schema *Schema, def *Definition) *gqlerror.Error {
}
}

switch def.Kind {
case Object, Interface:
if len(def.Fields) == 0 {
return gqlerror.ErrorPosf(def.Position, "%s must define one or more fields.", def.Kind)
}
case Enum:
if len(def.EnumValues) == 0 {
return gqlerror.ErrorPosf(def.Position, "%s must define one or more unique enum values.", def.Kind)
}
case InputObject:
if len(def.Fields) == 0 {
return gqlerror.ErrorPosf(def.Position, "%s must define one or more input fields.", def.Kind)
}
}

return validateDirectives(schema, def.Directives, nil)
}

Expand Down
83 changes: 83 additions & 0 deletions validator/schema_test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,27 @@ types:
message: "Cannot redeclare type A."
locations: [{line: 4, column: 6}]

object types:
- name: must define one or more fields
input: |
directive @D on OBJECT
# This pattern rejected by parser
# type InvalidObject1 {}
type InvalidObject2 @D
type ValidObject {
id: ID
}
extend type ValidObject @D
extend type ValidObject {
b: Int
}
error:
message: 'OBJECT must define one or more fields.'
locations: [{line: 6, column: 6}]

interfaces:
- name: must exist
input: |
Expand Down Expand Up @@ -42,6 +63,68 @@ interfaces:
message: '"Object" is a non interface type OBJECT.'
locations: [{line: 1, column: 6}]

- name: must define one or more fields
input: |
directive @D on INTERFACE
# This pattern rejected by parser
# interface InvalidInterface1 {}
interface InvalidInterface2 @D
interface ValidInterface {
id: ID
}
extend interface ValidInterface @D
extend interface ValidInterface {
b: Int
}
error:
message: 'INTERFACE must define one or more fields.'
locations: [{line: 6, column: 11}]

inputs:
- name: must define one or more input fields
input: |
directive @D on INPUT_OBJECT
# This pattern rejected by parser
# input InvalidInput1 {}
input InvalidInput2 @D
input ValidInput {
id: ID
}
extend input ValidInput @D
extend input ValidInput {
b: Int
}
error:
message: 'INPUT_OBJECT must define one or more input fields.'
locations: [{line: 6, column: 7}]

enums:
- name: must define one or more unique enum values
input: |
directive @D on ENUM
# This pattern rejected by parser
# enum InvalidEmum1 {}
enum InvalidEnum2 @D
enum ValidEnum {
FOO
}
extend enum ValidEnum @D
extend enum ValidEnum {
BAR
}
error:
message: 'ENUM must define one or more unique enum values.'
locations: [{line: 6, column: 6}]

type extensions:
- name: cannot extend non existant types
input: |
Expand Down

0 comments on commit 6f09700

Please sign in to comment.