Skip to content

Commit

Permalink
Merge pull request #97 from vektah/interface-validation
Browse files Browse the repository at this point in the history
Add more interface validation
  • Loading branch information
vektah authored Mar 11, 2019
2 parents 9541c8b + 652c449 commit 05741cd
Show file tree
Hide file tree
Showing 2 changed files with 180 additions and 6 deletions.
86 changes: 80 additions & 6 deletions validator/schema.go
Original file line number Diff line number Diff line change
Expand Up @@ -195,12 +195,8 @@ func validateDefinition(schema *Schema, def *Definition) *gqlerror.Error {
}

for _, intf := range def.Interfaces {
intDef := schema.Types[intf]
if intDef == nil {
return gqlerror.ErrorPosf(def.Position, "Undefined type %s.", strconv.Quote(intf))
}
if intDef.Kind != Interface {
return gqlerror.ErrorPosf(def.Position, "%s is a non interface type %s.", strconv.Quote(intf), intDef.Kind)
if err := validateImplements(schema, def, intf); err != nil {
return err
}
}

Expand Down Expand Up @@ -302,6 +298,84 @@ func validateDirectives(schema *Schema, dirs DirectiveList, currentDirective *Di
return nil
}

func validateImplements(schema *Schema, def *Definition, intfName string) *gqlerror.Error {
// see validation rules at the bottom of
// https://facebook.github.io/graphql/June2018/#sec-Objects
intf := schema.Types[intfName]
if intf == nil {
return gqlerror.ErrorPosf(def.Position, "Undefined type %s.", strconv.Quote(intfName))
}
if intf.Kind != Interface {
return gqlerror.ErrorPosf(def.Position, "%s is a non interface type %s.", strconv.Quote(intfName), intf.Kind)
}
for _, requiredField := range intf.Fields {
foundField := def.Fields.ForName(requiredField.Name)
if foundField == nil {
return gqlerror.ErrorPosf(def.Position,
`For %s to implement %s it must have a field called %s.`,
def.Name, intf.Name, requiredField.Name,
)
}

if !isCovariant(schema, requiredField.Type, foundField.Type) {
return gqlerror.ErrorPosf(foundField.Position,
`For %s to implement %s the field %s must have type %s.`,
def.Name, intf.Name, requiredField.Name, requiredField.Type.String(),
)
}

for _, requiredArg := range requiredField.Arguments {
foundArg := foundField.Arguments.ForName(requiredArg.Name)
if foundArg == nil {
return gqlerror.ErrorPosf(foundField.Position,
`For %s to implement %s the field %s must have the same arguments but it is missing %s.`,
def.Name, intf.Name, requiredField.Name, requiredArg.Name,
)
}

if !requiredArg.Type.IsCompatible(foundArg.Type) {
return gqlerror.ErrorPosf(foundArg.Position,
`For %s to implement %s the field %s must have the same arguments but %s has the wrong type.`,
def.Name, intf.Name, requiredField.Name, requiredArg.Name,
)
}
}
for _, foundArgs := range foundField.Arguments {
if requiredField.Arguments.ForName(foundArgs.Name) == nil && foundArgs.Type.NonNull && foundArgs.DefaultValue == nil {
return gqlerror.ErrorPosf(foundArgs.Position,
`For %s to implement %s any additional arguments on %s must be optional or have a default value but %s is required.`,
def.Name, intf.Name, foundField.Name, foundArgs.Name,
)
}
}
}
return nil
}

func isCovariant(schema *Schema, required *Type, actual *Type) bool {
if required.NonNull && !actual.NonNull {
return false
}

if required.NamedType != "" {
if required.NamedType == actual.NamedType {
return true
}
for _, pt := range schema.PossibleTypes[required.NamedType] {
if pt.Name == actual.NamedType {
return true
}
}
return false
}

if required.Elem != nil && actual.Elem == nil {
return false
}

return isCovariant(schema, required.Elem, actual.Elem)
}

func validateName(pos *Position, name string) *gqlerror.Error {
if strings.HasPrefix(name, "__") {
return gqlerror.ErrorPosf(pos, `Name "%s" must not begin with "__", which is reserved by GraphQL introspection.`, name)
Expand Down
100 changes: 100 additions & 0 deletions validator/schema_test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -175,6 +175,106 @@ interfaces:
message: 'INTERFACE field must be one of SCALAR, OBJECT, INTERFACE, UNION, ENUM.'
locations: [{line: 8, column: 3}]

- name: must have all fields from interface
input: |
type Bar implements BarInterface {
someField: Int!
}
interface BarInterface {
id: ID!
}
error:
message: 'For Bar to implement BarInterface it must have a field called id.'
locations: [{line: 1, column: 6}]

- name: must have same type of fields
input: |
type Bar implements BarInterface {
id: Int!
}
interface BarInterface {
id: ID!
}
error:
message: 'For Bar to implement BarInterface the field id must have type ID!.'
locations: [{line: 2, column: 5}]

- name: must have all required arguments
input: |
type Bar implements BarInterface {
id: ID!
}
interface BarInterface {
id(ff: Int!): ID!
}
error:
message: 'For Bar to implement BarInterface the field id must have the same arguments but it is missing ff.'
locations: [{line: 2, column: 5}]

- name: must have same argument types
input: |
type Bar implements BarInterface {
id(ff: ID!): ID!
}
interface BarInterface {
id(ff: Int!): ID!
}
error:
message: 'For Bar to implement BarInterface the field id must have the same arguments but ff has the wrong type.'
locations: [{line: 2, column: 8}]

- name: may defined additional nullable arguments
input: |
type Bar implements BarInterface {
id(opt: Int): ID!
}
interface BarInterface {
id: ID!
}
- name: may defined additional required arguments with defaults
input: |
type Bar implements BarInterface {
id(opt: Int! = 1): ID!
}
interface BarInterface {
id: ID!
}
- name: must not define additional required arguments without defaults
input: |
type Bar implements BarInterface {
id(opt: Int!): ID!
}
interface BarInterface {
id: ID!
}
error:
message: 'For Bar to implement BarInterface any additional arguments on id must be optional or have a default value but opt is required.'
locations: [{line: 2, column: 8}]

- name: can have covariant argument types
input: |
union U = A|B
type A { name: String }
type B { name: String }
type Bar implements BarInterface {
f: A!
}
interface BarInterface {
f: U!
}
inputs:
- name: must define one or more input fields
input: |
Expand Down

0 comments on commit 05741cd

Please sign in to comment.