Skip to content

Commit

Permalink
[TT-5279] Subgraphs with empty type bodies are invalidated before ext…
Browse files Browse the repository at this point in the history
…ension could occur

[changelog]
internal: Added visitor to return an error if a type has an empty body.
  • Loading branch information
David Stutt committed May 19, 2022
1 parent 9295cc4 commit 6820fd5
Show file tree
Hide file tree
Showing 6 changed files with 153 additions and 1 deletion.
27 changes: 27 additions & 0 deletions pkg/astnormalization/astnormalization_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -452,6 +452,33 @@ var run = func(normalizeFunc registerNormalizeFunc, definition, operation, expec
}
}

var runAndExpectError = func(t *testing.T, normalizeFunc registerNormalizeFunc, definition, operation, expectedError string) {
definitionDocument := unsafeparser.ParseGraphqlDocumentString(definition)
err := asttransform.MergeDefinitionWithBaseSchema(&definitionDocument)
if err != nil {
panic(err)
}

operationDocument := unsafeparser.ParseGraphqlDocumentString(operation)
report := operationreport.Report{}
walker := astvisitor.NewWalker(48)

normalizeFunc(&walker)

walker.Walk(&operationDocument, &definitionDocument, &report)

var got string
if report.HasErrors() {
if report.InternalErrors == nil {
got = report.ExternalErrors[0].Message
} else {
got = report.InternalErrors[0].Error()
}
}

assert.Equal(t, expectedError, got)
}

func runMany(definition, operation, expectedOutput string, normalizeFuncs ...registerNormalizeFunc) {
var runManyNormalizers = func(walker *astvisitor.Walker) {
for _, normalizeFunc := range normalizeFuncs {
Expand Down
1 change: 1 addition & 0 deletions pkg/astnormalization/definition_normalization.go
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@ func NewSubgraphDefinitionNormalizer() *DefinitionNormalizer {
func (o *DefinitionNormalizer) setupSubgraphWalkers() {
walker := astvisitor.NewWalker(48)

validateTypeBodyVisitor(&walker)
extendObjectTypeDefinitionKeepingOrphans(&walker)
extendInputObjectTypeDefinitionKeepingOrphans(&walker)
extendEnumTypeDefinitionKeepingOrphans(&walker)
Expand Down
80 changes: 80 additions & 0 deletions pkg/astnormalization/validate_type_body.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
package astnormalization

import (
"github.com/jensneuse/graphql-go-tools/pkg/ast"
"github.com/jensneuse/graphql-go-tools/pkg/astvisitor"
"github.com/jensneuse/graphql-go-tools/pkg/operationreport"
)

type validateTypeBody struct {
*astvisitor.Walker
operation *ast.Document
lastEnumRef int
lastInputRef int
lastInterfaceRef int
lastObjectRef int
}

func validateTypeBodyVisitor(walker *astvisitor.Walker) {
visitor := validateTypeBody{
Walker: walker,
operation: nil,
lastEnumRef: ast.InvalidRef,
lastInputRef: ast.InvalidRef,
lastInterfaceRef: ast.InvalidRef,
lastObjectRef: ast.InvalidRef,
}
walker.RegisterEnterDocumentVisitor(&visitor)
walker.RegisterEnterEnumTypeDefinitionVisitor(&visitor)
walker.RegisterEnterInputObjectTypeDefinitionVisitor(&visitor)
walker.RegisterEnterInterfaceTypeDefinitionVisitor(&visitor)
walker.RegisterEnterObjectTypeDefinitionVisitor(&visitor)
}

func (v *validateTypeBody) EnterDocument(operation, _ *ast.Document) {
v.operation = operation
}

func (v validateTypeBody) EnterEnumTypeDefinition(ref int) {
if v.lastEnumRef >= ref {
return
}
operation := v.operation
if !operation.EnumTypeDefinitions[ref].HasEnumValuesDefinition {
v.Walker.StopWithExternalErr(operationreport.ErrTypeBodyMustNotBeEmpty("enum", operation.EnumTypeDefinitionNameString(ref)))
}
v.lastEnumRef = ref
}

func (v validateTypeBody) EnterInputObjectTypeDefinition(ref int) {
if v.lastInputRef >= ref {
return
}
operation := v.operation
if !operation.InputObjectTypeDefinitions[ref].HasInputFieldsDefinition {
v.Walker.StopWithExternalErr(operationreport.ErrTypeBodyMustNotBeEmpty("input", operation.InputObjectTypeDefinitionNameString(ref)))
}
v.lastInputRef = ref
}

func (v validateTypeBody) EnterInterfaceTypeDefinition(ref int) {
if v.lastInterfaceRef >= ref {
return
}
operation := v.operation
if !operation.InterfaceTypeDefinitions[ref].HasFieldDefinitions {
v.Walker.StopWithExternalErr(operationreport.ErrTypeBodyMustNotBeEmpty("interface", operation.InterfaceTypeDefinitionNameString(ref)))
}
v.lastInterfaceRef = ref
}

func (v validateTypeBody) EnterObjectTypeDefinition(ref int) {
if v.lastObjectRef >= ref {
return
}
operation := v.operation
if !operation.ObjectTypeDefinitions[ref].HasFieldDefinitions {
v.Walker.StopWithExternalErr(operationreport.ErrTypeBodyMustNotBeEmpty("object", operation.ObjectTypeDefinitionNameString(ref)))
}
v.lastObjectRef = ref
}
40 changes: 40 additions & 0 deletions pkg/astnormalization/validate_type_body_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
package astnormalization

import (
"fmt"
"testing"
)

func TestCheckUnresolvedExtensionOrphans(t *testing.T) {
t.Run("Empty enum body returns an error", func(t *testing.T) {
runAndExpectError(t, validateTypeBodyVisitor, testDefinition, `
enum Badges {
}
`, EmptyTypeBodyErrorMessage("enum", "Badges"))
})

t.Run("Empty input body returns an error", func(t *testing.T) {
runAndExpectError(t, validateTypeBodyVisitor, testDefinition, `
input Badges {
}
`, EmptyTypeBodyErrorMessage("input", "Badges"))
})

t.Run("Empty interface body returns an error", func(t *testing.T) {
runAndExpectError(t, validateTypeBodyVisitor, testDefinition, `
interface Badges {
}
`, EmptyTypeBodyErrorMessage("interface", "Badges"))
})

t.Run("Empty object body returns an error", func(t *testing.T) {
runAndExpectError(t, validateTypeBodyVisitor, testDefinition, `
type Badges {
}
`, EmptyTypeBodyErrorMessage("object", "Badges"))
})
}

func EmptyTypeBodyErrorMessage(definitionType, typeName string) string {
return fmt.Sprintf("the %s named '%s' is invalid due to an empty body", definitionType, typeName)
}
1 change: 0 additions & 1 deletion pkg/federation/sdlmerge/check_extension_orphans.go
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,6 @@ func (p *checkUnresolvedExtensionOrphansVisitor) Register(walker *astvisitor.Wal
}

func (p *checkUnresolvedExtensionOrphansVisitor) EnterDocument(operation, _ *ast.Document) {
//operation.DeleteRootNodes(operation.Index.MergedTypeExtensions)
p.document = operation
}

Expand Down
5 changes: 5 additions & 0 deletions pkg/operationreport/externalerror.go
Original file line number Diff line number Diff line change
Expand Up @@ -288,3 +288,8 @@ func ErrExtensionOrphansMustResolveInSupergraph(extensionNameBytes []byte) (err
err.Message = fmt.Sprintf("the extension orphan named '%s' was never resolved in the supergraph", extensionNameBytes)
return err
}

func ErrTypeBodyMustNotBeEmpty(definitionType, typeName string) (err ExternalError) {
err.Message = fmt.Sprintf("the %s named '%s' is invalid due to an empty body", definitionType, typeName)
return err
}

0 comments on commit 6820fd5

Please sign in to comment.