Skip to content

Feat/allow interface to implement interface #268

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

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
27 changes: 20 additions & 7 deletions pkg/ast/ast_interface_type_definition.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
package ast

import (
"bytes"

"github.com/jensneuse/graphql-go-tools/internal/pkg/unsafebytes"
"github.com/jensneuse/graphql-go-tools/pkg/lexer/position"
)
Expand All @@ -11,13 +13,14 @@ import (
// name: String
// }
type InterfaceTypeDefinition struct {
Description Description // optional, describes the interface
InterfaceLiteral position.Position // interface
Name ByteSliceReference // e.g. NamedEntity
HasDirectives bool
Directives DirectiveList // optional, e.g. @foo
HasFieldDefinitions bool
FieldsDefinition FieldDefinitionList // optional, e.g. { name: String }
Description Description // optional, describes the interface
InterfaceLiteral position.Position // interface
Name ByteSliceReference // e.g. NamedEntity
ImplementsInterfaces TypeList // e.g implements Bar & Baz
HasDirectives bool
Directives DirectiveList // optional, e.g. @foo
HasFieldDefinitions bool
FieldsDefinition FieldDefinitionList // optional, e.g. { name: String }
}

func (d *Document) InterfaceTypeDefinitionNameBytes(ref int) ByteSlice {
Expand All @@ -35,6 +38,16 @@ func (d *Document) InterfaceTypeDefinitionDescriptionBytes(ref int) ByteSlice {
return d.Input.ByteSlice(d.InterfaceTypeDefinitions[ref].Description.Content)
}

func (d *Document) InterfaceTypeDefinitionImplementsInterface(definitionRef int, interfaceName ByteSlice) bool {
for _, iRef := range d.InterfaceTypeDefinitions[definitionRef].ImplementsInterfaces.Refs {
implements := d.ResolveTypeNameBytes(iRef)
if bytes.Equal(interfaceName, implements) {
return true
}
}
return false
}

func (d *Document) InterfaceTypeDefinitionDescriptionString(ref int) string {
return unsafebytes.BytesToString(d.InterfaceTypeDefinitionDescriptionBytes(ref))
}
Expand Down
6 changes: 6 additions & 0 deletions pkg/astparser/parser.go
Original file line number Diff line number Diff line change
Expand Up @@ -1207,6 +1207,9 @@ func (p *Parser) parseInterfaceTypeDefinition(description *ast.Description) {
interfaceTypeDefinition.Directives = p.parseDirectiveList()
interfaceTypeDefinition.HasDirectives = len(interfaceTypeDefinition.Directives.Refs) > 0
}
if p.peekEqualsIdentKey(identkeyword.IMPLEMENTS) {
interfaceTypeDefinition.ImplementsInterfaces = p.parseImplementsInterfaces()
}
if p.peekEquals(keyword.LBRACE) {
interfaceTypeDefinition.FieldsDefinition = p.parseFieldDefinitionList()
interfaceTypeDefinition.HasFieldDefinitions = len(interfaceTypeDefinition.FieldsDefinition.Refs) > 0
Expand Down Expand Up @@ -1842,6 +1845,9 @@ func (p *Parser) parseInterfaceTypeExtension(extend position.Position) {
var interfaceTypeDefinition ast.InterfaceTypeDefinition
interfaceTypeDefinition.InterfaceLiteral = p.mustReadIdentKey(identkeyword.INTERFACE).TextPosition
interfaceTypeDefinition.Name = p.mustRead(keyword.IDENT).Literal
if p.peekEqualsIdentKey(identkeyword.IMPLEMENTS) {
interfaceTypeDefinition.ImplementsInterfaces = p.parseImplementsInterfaces()
}
if p.peekEquals(keyword.AT) {
interfaceTypeDefinition.Directives = p.parseDirectiveList()
interfaceTypeDefinition.HasDirectives = len(interfaceTypeDefinition.Directives.Refs) > 0
Expand Down
61 changes: 60 additions & 1 deletion pkg/astparser/parser_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -379,7 +379,7 @@ func TestParser_Parse(t *testing.T) {
})
t.Run("interface type extension", func(t *testing.T) {
t.Run("simple", func(t *testing.T) {
run(`extend interface NamedEntity @foo {
run(`extend interface NamedEntity {
name: String
}`, parse, false,
func(doc *ast.Document, extra interface{}) {
Expand All @@ -401,6 +401,39 @@ func TestParser_Parse(t *testing.T) {
}
})
})
t.Run("with interface implementation", func(t *testing.T) {
run(`extend interface NamedEntity implements Foo & Bar {
name: String
}`, parse, false,
func(doc *ast.Document, extra interface{}) {
namedEntity := doc.InterfaceTypeExtensions[0]
if doc.Input.ByteSliceString(namedEntity.Name) != "NamedEntity" {
panic("want NamedEntity")
}

implementsFoo := doc.Types[namedEntity.ImplementsInterfaces.Refs[0]]
if implementsFoo.TypeKind != ast.TypeKindNamed {
panic("want TypeKindNamed")
}
if doc.Input.ByteSliceString(implementsFoo.Name) != "Foo" {
panic("want Foo")
}

implementsBar := doc.Types[namedEntity.ImplementsInterfaces.Refs[1]]
if implementsBar.TypeKind != ast.TypeKindNamed {
panic("want TypeKindNamed")
}
if doc.Input.ByteSliceString(implementsBar.Name) != "Bar" {
panic("want Bar")
}

// fields
name := doc.FieldDefinitions[namedEntity.FieldsDefinition.Refs[0]]
if doc.Input.ByteSliceString(name.Name) != "name" {
panic("want name")
}
})
})
})
t.Run("scalar type extension", func(t *testing.T) {
t.Run("simple", func(t *testing.T) {
Expand Down Expand Up @@ -902,6 +935,32 @@ func TestParser_Parse(t *testing.T) {
}
})
})
t.Run("with interface implementation", func(t *testing.T) {
run(`interface NamedEntity implements Foo & Bar {
name: String
}`, parse, false,
func(doc *ast.Document, extra interface{}) {
namedEntity := doc.InterfaceTypeDefinitions[0]
if doc.Input.ByteSliceString(namedEntity.Name) != "NamedEntity" {
panic("want NamedEntity")
}
implementsFoo := doc.Types[namedEntity.ImplementsInterfaces.Refs[0]]
if implementsFoo.TypeKind != ast.TypeKindNamed {
panic("want TypeKindNamed")
}
if doc.Input.ByteSliceString(implementsFoo.Name) != "Foo" {
panic("want Foo")
}

implementsBar := doc.Types[namedEntity.ImplementsInterfaces.Refs[1]]
if implementsBar.TypeKind != ast.TypeKindNamed {
panic("want TypeKindNamed")
}
if doc.Input.ByteSliceString(implementsBar.Name) != "Bar" {
panic("want Bar")
}
})
})
})
t.Run("union type definition", func(t *testing.T) {
t.Run("simple", func(t *testing.T) {
Expand Down