-
Notifications
You must be signed in to change notification settings - Fork 121
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
[TT-5279] Moved empty type body validation to astvalidation
[changelog] internal: Moved visitor to astvalidation. Added validation for type extensions. Handled some edge cases.
- Loading branch information
David Stutt
committed
May 19, 2022
1 parent
6820fd5
commit a8b8544
Showing
6 changed files
with
359 additions
and
11 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,136 @@ | ||
package astvalidation | ||
|
||
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 populatedTypeBodiesVisitor struct { | ||
*astvisitor.Walker | ||
definition *ast.Document | ||
} | ||
|
||
func PopulatedTypeBodies() Rule { | ||
return func(walker *astvisitor.Walker) { | ||
visitor := &populatedTypeBodiesVisitor{ | ||
Walker: walker, | ||
definition: nil, | ||
} | ||
|
||
walker.RegisterEnterDocumentVisitor(visitor) | ||
walker.RegisterEnterEnumTypeDefinitionVisitor(visitor) | ||
walker.RegisterEnterEnumTypeExtensionVisitor(visitor) | ||
walker.RegisterEnterInputObjectTypeDefinitionVisitor(visitor) | ||
walker.RegisterEnterInputObjectTypeExtensionVisitor(visitor) | ||
walker.RegisterEnterInterfaceTypeDefinitionVisitor(visitor) | ||
walker.RegisterEnterInterfaceTypeExtensionVisitor(visitor) | ||
walker.RegisterEnterObjectTypeDefinitionVisitor(visitor) | ||
walker.RegisterEnterObjectTypeExtensionVisitor(visitor) | ||
} | ||
} | ||
|
||
func (p *populatedTypeBodiesVisitor) EnterDocument(operation, _ *ast.Document) { | ||
p.definition = operation | ||
} | ||
|
||
func (p populatedTypeBodiesVisitor) EnterEnumTypeDefinition(ref int) { | ||
definition := p.definition | ||
if !definition.EnumTypeDefinitions[ref].HasEnumValuesDefinition { | ||
p.Report.AddExternalError(operationreport.ErrTypeBodyMustNotBeEmpty("enum", definition.EnumTypeDefinitionNameString(ref))) | ||
return | ||
} | ||
} | ||
|
||
func (p *populatedTypeBodiesVisitor) EnterEnumTypeExtension(ref int) { | ||
definition := p.definition | ||
if !definition.EnumTypeExtensions[ref].HasEnumValuesDefinition { | ||
p.Report.AddExternalError(operationreport.ErrTypeBodyMustNotBeEmpty("enum extension", definition.EnumTypeExtensionNameString(ref))) | ||
return | ||
} | ||
} | ||
|
||
func (p populatedTypeBodiesVisitor) EnterInputObjectTypeDefinition(ref int) { | ||
definition := p.definition | ||
if !definition.InputObjectTypeDefinitions[ref].HasInputFieldsDefinition { | ||
p.Report.AddExternalError(operationreport.ErrTypeBodyMustNotBeEmpty("input", definition.InputObjectTypeDefinitionNameString(ref))) | ||
return | ||
} | ||
} | ||
|
||
func (p *populatedTypeBodiesVisitor) EnterInputObjectTypeExtension(ref int) { | ||
definition := p.definition | ||
if !definition.InputObjectTypeExtensions[ref].HasInputFieldsDefinition { | ||
p.Report.AddExternalError(operationreport.ErrTypeBodyMustNotBeEmpty("input extension", definition.InputObjectTypeExtensionNameString(ref))) | ||
return | ||
} | ||
} | ||
|
||
func (p populatedTypeBodiesVisitor) EnterInterfaceTypeDefinition(ref int) { | ||
definition := p.definition | ||
switch definition.InterfaceTypeDefinitions[ref].HasFieldDefinitions { | ||
case true: | ||
refs := definition.InterfaceTypeDefinitions[ref].FieldsDefinition.Refs | ||
if len(refs) > 1 || definition.FieldDefinitionNameString(refs[0]) != typename { | ||
return | ||
} | ||
fallthrough | ||
case false: | ||
p.Report.AddExternalError(operationreport.ErrTypeBodyMustNotBeEmpty("interface", definition.InterfaceTypeDefinitionNameString(ref))) | ||
return | ||
} | ||
} | ||
|
||
func (p *populatedTypeBodiesVisitor) EnterInterfaceTypeExtension(ref int) { | ||
definition := p.definition | ||
if !definition.InterfaceTypeExtensions[ref].HasFieldDefinitions { | ||
p.Report.AddExternalError(operationreport.ErrTypeBodyMustNotBeEmpty("interface extension", definition.InterfaceTypeExtensionNameString(ref))) | ||
return | ||
} | ||
} | ||
|
||
func (p populatedTypeBodiesVisitor) EnterObjectTypeDefinition(ref int) { | ||
definition := p.definition | ||
nameBytes := definition.ObjectTypeDefinitionNameBytes(ref) | ||
if isRootType(nameBytes) { | ||
return | ||
} | ||
switch definition.ObjectTypeDefinitions[ref].HasFieldDefinitions { | ||
case true: | ||
refs := definition.ObjectTypeDefinitions[ref].FieldsDefinition.Refs | ||
if len(refs) > 1 || definition.FieldDefinitionNameString(refs[0]) != typename { | ||
return | ||
} | ||
fallthrough | ||
case false: | ||
p.Report.AddExternalError(operationreport.ErrTypeBodyMustNotBeEmpty("object", string(nameBytes))) | ||
return | ||
} | ||
} | ||
|
||
func (p *populatedTypeBodiesVisitor) EnterObjectTypeExtension(ref int) { | ||
definition := p.definition | ||
if !definition.ObjectTypeExtensions[ref].HasFieldDefinitions { | ||
p.Report.AddExternalError(operationreport.ErrTypeBodyMustNotBeEmpty("object extension", definition.ObjectTypeExtensionNameString(ref))) | ||
return | ||
} | ||
} | ||
|
||
func isRootType(nameBytes []byte) bool { | ||
length := len(nameBytes) | ||
return isQuery(length, nameBytes) || isMutation(length, nameBytes) || isSubscription(length, nameBytes) | ||
} | ||
|
||
func isQuery(length int, b []byte) bool { | ||
return length == 5 && b[0] == 'Q' && b[1] == 'u' && b[2] == 'e' && b[3] == 'r' && b[4] == 'y' | ||
} | ||
|
||
func isMutation(length int, b []byte) bool { | ||
return length == 8 && b[0] == 'M' && b[1] == 'u' && b[2] == 't' && b[3] == 'a' && b[4] == 't' && b[5] == 'i' && b[6] == 'o' && b[7] == 'n' | ||
} | ||
|
||
func isSubscription(length int, b []byte) bool { | ||
return length == 12 && b[0] == 'S' && b[1] == 'u' && b[2] == 'b' && b[3] == 's' && b[4] == 'c' && b[5] == 'r' && b[6] == 'i' && b[7] == 'p' && b[8] == 't' && b[9] == 'i' && b[10] == 'o' && b[11] == 'n' | ||
} | ||
|
||
const typename = "__typename" |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,126 @@ | ||
package astvalidation | ||
|
||
import ( | ||
"testing" | ||
) | ||
|
||
func TestPopulatedTypeBodies(t *testing.T) { | ||
t.Run("Definition", func(t *testing.T) { | ||
t.Run("Populated type bodies are valid", func(t *testing.T) { | ||
runDefinitionValidation(t, ` | ||
enum Species { | ||
CAT | ||
} | ||
extend enum Color { | ||
DOG | ||
} | ||
input Message { | ||
content: String! | ||
} | ||
extend input Message { | ||
updated: DateTime! | ||
} | ||
interface Animal { | ||
species: Species! | ||
} | ||
extend interface Animal { | ||
age: Int! | ||
} | ||
type Cat implements Animal { | ||
species: Species! | ||
} | ||
extend type Cat implements Animal { | ||
age: Int! | ||
} | ||
`, Valid, PopulatedTypeBodies(), | ||
) | ||
}) | ||
|
||
t.Run("Empty enum is invalid", func(t *testing.T) { | ||
runDefinitionValidation(t, ` | ||
enum Species { | ||
} | ||
`, Invalid, PopulatedTypeBodies(), | ||
) | ||
}) | ||
|
||
t.Run("Empty enum extension is invalid", func(t *testing.T) { | ||
runDefinitionValidation(t, ` | ||
enum Species { | ||
CAT | ||
} | ||
extend enum Species { | ||
} | ||
`, Invalid, PopulatedTypeBodies(), | ||
) | ||
}) | ||
|
||
t.Run("Empty input is invalid", func(t *testing.T) { | ||
runDefinitionValidation(t, ` | ||
input Message { | ||
} | ||
`, Invalid, PopulatedTypeBodies(), | ||
) | ||
}) | ||
|
||
t.Run("Empty input extension is invalid", func(t *testing.T) { | ||
runDefinitionValidation(t, ` | ||
input Message { | ||
content: String! | ||
} | ||
extend input Message { | ||
} | ||
`, Invalid, PopulatedTypeBodies(), | ||
) | ||
}) | ||
|
||
t.Run("Empty interface is invalid", func(t *testing.T) { | ||
runDefinitionValidation(t, ` | ||
interface Animal { | ||
} | ||
`, Invalid, PopulatedTypeBodies(), | ||
) | ||
}) | ||
|
||
t.Run("Empty interface extension is invalid", func(t *testing.T) { | ||
runDefinitionValidation(t, ` | ||
interface Animal { | ||
species: String! | ||
} | ||
extend interface Animal { | ||
} | ||
`, Invalid, PopulatedTypeBodies(), | ||
) | ||
}) | ||
|
||
t.Run("Empty object is invalid", func(t *testing.T) { | ||
runDefinitionValidation(t, ` | ||
type Cat { | ||
} | ||
`, Invalid, PopulatedTypeBodies(), | ||
) | ||
}) | ||
|
||
t.Run("Empty object extension is invalid", func(t *testing.T) { | ||
runDefinitionValidation(t, ` | ||
type Cat { | ||
species: String! | ||
} | ||
extend type Cat { | ||
} | ||
`, Invalid, PopulatedTypeBodies(), | ||
) | ||
}) | ||
}) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.