Skip to content

Commit

Permalink
add name validation
Browse files Browse the repository at this point in the history
  • Loading branch information
aaronc committed Jun 19, 2024
1 parent 0b6dfbd commit 85e5114
Show file tree
Hide file tree
Showing 12 changed files with 78 additions and 29 deletions.
13 changes: 8 additions & 5 deletions indexer/base/enum.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,26 +4,29 @@ import "fmt"

// EnumDefinition represents the definition of an enum type.
type EnumDefinition struct {
// Name is the name of the enum type.
// Name is the name of the enum type. It must conform to the NameFormat regular expression.
Name string

// Values is a list of distinct, non-empty values that are part of the enum type.
// Each value must conform to the NameFormat regular expression.
Values []string
}

// Validate validates the enum definition.
func (e EnumDefinition) Validate() error {
if e.Name == "" {
return fmt.Errorf("enum definition name cannot be empty")
if !ValidateName(e.Name) {
return fmt.Errorf("invalid enum definition name %q", e.Name)
}

if len(e.Values) == 0 {
return fmt.Errorf("enum definition values cannot be empty")
}
seen := make(map[string]bool, len(e.Values))
for i, v := range e.Values {
if v == "" {
return fmt.Errorf("enum definition value at index %d cannot be empty for enum %s", i, e.Name)
if !ValidateName(v) {
return fmt.Errorf("invalid enum definition value %q at index %d for enum %s", v, i, e.Name)
}

if seen[v] {
return fmt.Errorf("duplicate enum definition value %q for enum %s", v, e.Name)
}
Expand Down
4 changes: 2 additions & 2 deletions indexer/base/enum_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ func TestEnumDefinition_Validate(t *testing.T) {
Name: "",
Values: []string{"a", "b", "c"},
},
errContains: "enum definition name cannot be empty",
errContains: "invalid enum definition name",
},
{
name: "empty values",
Expand All @@ -41,7 +41,7 @@ func TestEnumDefinition_Validate(t *testing.T) {
Name: "test",
Values: []string{"a", "", "c"},
},
errContains: "enum definition value at index 1 cannot be empty for enum test",
errContains: "invalid enum definition value",
},
{
name: "duplicate value",
Expand Down
8 changes: 4 additions & 4 deletions indexer/base/field.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import "fmt"

// Field represents a field in an object type.
type Field struct {
// Name is the name of the field.
// Name is the name of the field. It must conform to the NameFormat regular expression.
Name string

// Kind is the basic type of the field.
Expand All @@ -25,9 +25,9 @@ type Field struct {

// Validate validates the field.
func (c Field) Validate() error {
// non-empty name
if c.Name == "" {
return fmt.Errorf("field name cannot be empty")
// valid name
if !ValidateName(c.Name) {
return fmt.Errorf("invalid field name %q", c.Name)
}

// valid kind
Expand Down
4 changes: 2 additions & 2 deletions indexer/base/field_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ func TestField_Validate(t *testing.T) {
Name: "",
Kind: StringKind,
},
errContains: "field name cannot be empty",
errContains: "invalid field name",
},
{
name: "invalid kind",
Expand Down Expand Up @@ -58,7 +58,7 @@ func TestField_Validate(t *testing.T) {
Name: "field1",
Kind: EnumKind,
},
errContains: "invalid enum definition for field \"field1\": enum definition name cannot be empty",
errContains: "invalid enum definition",
},
{
name: "enum definition with non-EnumKind",
Expand Down
3 changes: 3 additions & 0 deletions indexer/base/kind.go
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,9 @@ const (
JSONKind
)

// MAX_VALID_KIND is the maximum valid kind value.
const MAX_VALID_KIND = JSONKind

const (
IntegerFormat = `^-?[0-9]+$`
DecimalFormat = `^-?[0-9]+(\.[0-9]+)?([eE][-+]?[0-9]+)?$`
Expand Down
9 changes: 2 additions & 7 deletions indexer/base/kind_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,13 +7,8 @@ import (
"time"
)

var validKinds = []Kind{
StringKind, BytesKind, Int8Kind, Uint8Kind, Int16Kind, Uint16Kind, Int32Kind, Uint32Kind, Int64Kind,
Uint64Kind, IntegerKind, DecimalKind, BoolKind, EnumKind, Bech32AddressKind,
}

func TestKind_Validate(t *testing.T) {
for _, kind := range validKinds {
for kind := InvalidKind + 1; kind <= MAX_VALID_KIND; kind++ {
if err := kind.Validate(); err != nil {
t.Errorf("expected valid kind %s to pass validation, got: %v", kind, err)
}
Expand Down Expand Up @@ -97,7 +92,7 @@ func TestKind_ValidateValueType(t *testing.T) {
}

// nils get rejected
for _, kind := range validKinds {
for kind := InvalidKind + 1; kind <= MAX_VALID_KIND; kind++ {
if err := kind.ValidateValueType(nil); err == nil {
t.Errorf("expected nil value to fail validation for kind %s", kind)
}
Expand Down
5 changes: 3 additions & 2 deletions indexer/base/listener.go
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ type Listener struct {
OnEvent func(EventData) error

// OnKVPair is called when a key-value has been written to the store for a given module.
// Module names must conform to the NameFormat regular expression.
OnKVPair func(moduleName string, key, value []byte, delete bool) error

// Commit is called when state is committed, usually at the end of a block. Any
Expand All @@ -43,12 +44,12 @@ type Listener struct {
// should ensure that they have performed whatever initialization steps (such as database
// migrations) required to receive OnObjectUpdate events for the given module. If the
// indexer's schema is incompatible with the module's on-chain schema, the listener should return
// an error.
// an error. Module names must conform to the NameFormat regular expression.
InitializeModuleSchema func(module string, schema ModuleSchema) error

// OnObjectUpdate is called whenever an object is updated in a module's state. This is only called
// when logical data is available. It should be assumed that the same data in raw form
// is also passed to OnKVPair.
// is also passed to OnKVPair. Module names must conform to the NameFormat regular expression.
OnObjectUpdate func(module string, update ObjectUpdate) error
}

Expand Down
2 changes: 1 addition & 1 deletion indexer/base/module_schema_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ func TestModuleSchema_Validate(t *testing.T) {
},
},
},
errContains: "object type name cannot be empty",
errContains: "invalid object type name",
},
{
name: "same enum with missing values",
Expand Down
15 changes: 15 additions & 0 deletions indexer/base/name.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
package indexerbase

import "regexp"

// NameFormat is the regular expression that a name must match.
// A name must start with a letter or underscore and can only contain letters, numbers, and underscores.
// A name must be at least one character long and can be at most 64 characters long.
const NameFormat = `^[a-zA-Z_][a-zA-Z0-9_]{0,63}$`

var nameRegex = regexp.MustCompile(NameFormat)

// ValidateName checks if the given name is a valid name conforming to NameFormat.
func ValidateName(name string) bool {
return nameRegex.MatchString(name)
}
31 changes: 31 additions & 0 deletions indexer/base/name_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
package indexerbase

import "testing"

func TestValidateName(t *testing.T) {
tests := []struct {
name string
valid bool
}{
{"", false},
{"a", true},
{"A", true},
{"_", true},
{"abc123_def789", true},
{"0", false},
{"a0", true},
{"a_", true},
{"$a", false},
{"a b", false},
{"pretty_unnecessarily_long_but_valid_name", true},
{"totally_unnecessarily_long_and_invalid_name_sdgkhwersdglkhweriqwery3258", false},
}

for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
if ValidateName(test.name) != test.valid {
t.Errorf("expected %v for name %q", test.valid, test.name)
}
})
}
}
7 changes: 4 additions & 3 deletions indexer/base/object_type.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,8 @@ import "fmt"

// ObjectType describes an object type a module schema.
type ObjectType struct {
// Name is the name of the object.
// Name is the name of the object type. It must be unique within the module schema
// and conform to the NameFormat regular expression.
Name string

// KeyFields is a list of fields that make up the primary key of the object.
Expand All @@ -28,8 +29,8 @@ type ObjectType struct {

// Validate validates the object type.
func (o ObjectType) Validate() error {
if o.Name == "" {
return fmt.Errorf("object type name cannot be empty")
if !ValidateName(o.Name) {
return fmt.Errorf("invalid object type name %q", o.Name)
}

fieldNames := map[string]bool{}
Expand Down
6 changes: 3 additions & 3 deletions indexer/base/object_type_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -80,7 +80,7 @@ func TestObjectType_Validate(t *testing.T) {
},
},
},
errContains: "object type name cannot be empty",
errContains: "invalid object type name",
},
{
name: "invalid key field",
Expand All @@ -93,7 +93,7 @@ func TestObjectType_Validate(t *testing.T) {
},
},
},
errContains: "field name cannot be empty",
errContains: "invalid field name",
},
{
name: "invalid value field",
Expand All @@ -105,7 +105,7 @@ func TestObjectType_Validate(t *testing.T) {
},
},
},
errContains: "field name cannot be empty",
errContains: "invalid field name",
},
{
name: "no fields",
Expand Down

0 comments on commit 85e5114

Please sign in to comment.