feat(schema)!: add more range methods, rename EnumDefinition -> EnumType (#21043)
This commit is contained in:
parent
1f0ce2f5b5
commit
339e26ea8f
@ -5,8 +5,8 @@ import (
|
||||
"fmt"
|
||||
)
|
||||
|
||||
// EnumDefinition represents the definition of an enum type.
|
||||
type EnumDefinition struct {
|
||||
// EnumType represents the definition of an enum type.
|
||||
type EnumType struct {
|
||||
// Name is the name of the enum type. It must conform to the NameFormat regular expression.
|
||||
// Its name must be unique between all enum types and object types in the module.
|
||||
// The same enum, however, can be used in multiple object types and fields as long as the
|
||||
@ -18,10 +18,15 @@ type EnumDefinition struct {
|
||||
Values []string
|
||||
}
|
||||
|
||||
func (EnumDefinition) isType() {}
|
||||
// TypeName implements the Type interface.
|
||||
func (e EnumType) TypeName() string {
|
||||
return e.Name
|
||||
}
|
||||
|
||||
func (EnumType) isType() {}
|
||||
|
||||
// Validate validates the enum definition.
|
||||
func (e EnumDefinition) Validate() error {
|
||||
func (e EnumType) Validate() error {
|
||||
if !ValidateName(e.Name) {
|
||||
return fmt.Errorf("invalid enum definition name %q", e.Name)
|
||||
}
|
||||
@ -44,7 +49,7 @@ func (e EnumDefinition) Validate() error {
|
||||
}
|
||||
|
||||
// ValidateValue validates that the value is a valid enum value.
|
||||
func (e EnumDefinition) ValidateValue(value string) error {
|
||||
func (e EnumType) ValidateValue(value string) error {
|
||||
for _, v := range e.Values {
|
||||
if v == value {
|
||||
return nil
|
||||
|
||||
@ -8,12 +8,12 @@ import (
|
||||
func TestEnumDefinition_Validate(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
enum EnumDefinition
|
||||
enum EnumType
|
||||
errContains string
|
||||
}{
|
||||
{
|
||||
name: "valid enum",
|
||||
enum: EnumDefinition{
|
||||
enum: EnumType{
|
||||
Name: "test",
|
||||
Values: []string{"a", "b", "c"},
|
||||
},
|
||||
@ -21,7 +21,7 @@ func TestEnumDefinition_Validate(t *testing.T) {
|
||||
},
|
||||
{
|
||||
name: "empty name",
|
||||
enum: EnumDefinition{
|
||||
enum: EnumType{
|
||||
Name: "",
|
||||
Values: []string{"a", "b", "c"},
|
||||
},
|
||||
@ -29,7 +29,7 @@ func TestEnumDefinition_Validate(t *testing.T) {
|
||||
},
|
||||
{
|
||||
name: "empty values",
|
||||
enum: EnumDefinition{
|
||||
enum: EnumType{
|
||||
Name: "test",
|
||||
Values: []string{},
|
||||
},
|
||||
@ -37,7 +37,7 @@ func TestEnumDefinition_Validate(t *testing.T) {
|
||||
},
|
||||
{
|
||||
name: "empty value",
|
||||
enum: EnumDefinition{
|
||||
enum: EnumType{
|
||||
Name: "test",
|
||||
Values: []string{"a", "", "c"},
|
||||
},
|
||||
@ -45,7 +45,7 @@ func TestEnumDefinition_Validate(t *testing.T) {
|
||||
},
|
||||
{
|
||||
name: "duplicate value",
|
||||
enum: EnumDefinition{
|
||||
enum: EnumType{
|
||||
Name: "test",
|
||||
Values: []string{"a", "b", "a"},
|
||||
},
|
||||
@ -72,7 +72,7 @@ func TestEnumDefinition_Validate(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestEnumDefinition_ValidateValue(t *testing.T) {
|
||||
enum := EnumDefinition{
|
||||
enum := EnumType{
|
||||
Name: "test",
|
||||
Values: []string{"a", "b", "c"},
|
||||
}
|
||||
|
||||
@ -13,11 +13,11 @@ type Field struct {
|
||||
// Nullable indicates whether null values are accepted for the field. Key fields CANNOT be nullable.
|
||||
Nullable bool
|
||||
|
||||
// EnumDefinition is the definition of the enum type and is only valid when Kind is EnumKind.
|
||||
// EnumType is the definition of the enum type and is only valid when Kind is EnumKind.
|
||||
// The same enum types can be reused in the same module schema, but they always must contain
|
||||
// the same values for the same enum name. This possibly introduces some duplication of
|
||||
// definitions but makes it easier to reason about correctness and validation in isolation.
|
||||
EnumDefinition EnumDefinition
|
||||
EnumType EnumType
|
||||
}
|
||||
|
||||
// Validate validates the field.
|
||||
@ -34,10 +34,10 @@ func (c Field) Validate() error {
|
||||
|
||||
// enum definition only valid with EnumKind
|
||||
if c.Kind == EnumKind {
|
||||
if err := c.EnumDefinition.Validate(); err != nil {
|
||||
if err := c.EnumType.Validate(); err != nil {
|
||||
return fmt.Errorf("invalid enum definition for field %q: %v", c.Name, err) //nolint:errorlint // false positive due to using go1.12
|
||||
}
|
||||
} else if c.Kind != EnumKind && (c.EnumDefinition.Name != "" || c.EnumDefinition.Values != nil) {
|
||||
} else if c.Kind != EnumKind && (c.EnumType.Name != "" || c.EnumType.Values != nil) {
|
||||
return fmt.Errorf("enum definition is only valid for field %q with type EnumKind", c.Name)
|
||||
}
|
||||
|
||||
@ -45,7 +45,7 @@ func (c Field) Validate() error {
|
||||
}
|
||||
|
||||
// ValidateValue validates that the value conforms to the field's kind and nullability.
|
||||
// Unlike Kind.ValidateValue, it also checks that the value conforms to the EnumDefinition
|
||||
// Unlike Kind.ValidateValue, it also checks that the value conforms to the EnumType
|
||||
// if the field is an EnumKind.
|
||||
func (c Field) ValidateValue(value interface{}) error {
|
||||
if value == nil {
|
||||
@ -60,7 +60,7 @@ func (c Field) ValidateValue(value interface{}) error {
|
||||
}
|
||||
|
||||
if c.Kind == EnumKind {
|
||||
return c.EnumDefinition.ValidateValue(value.(string))
|
||||
return c.EnumType.ValidateValue(value.(string))
|
||||
}
|
||||
|
||||
return nil
|
||||
|
||||
@ -46,18 +46,18 @@ func TestField_Validate(t *testing.T) {
|
||||
{
|
||||
name: "enum definition with non-EnumKind",
|
||||
field: Field{
|
||||
Name: "field1",
|
||||
Kind: StringKind,
|
||||
EnumDefinition: EnumDefinition{Name: "enum"},
|
||||
Name: "field1",
|
||||
Kind: StringKind,
|
||||
EnumType: EnumType{Name: "enum"},
|
||||
},
|
||||
errContains: "enum definition is only valid for field \"field1\" with type EnumKind",
|
||||
},
|
||||
{
|
||||
name: "valid enum",
|
||||
field: Field{
|
||||
Name: "field1",
|
||||
Kind: EnumKind,
|
||||
EnumDefinition: EnumDefinition{Name: "enum", Values: []string{"a", "b"}},
|
||||
Name: "field1",
|
||||
Kind: EnumKind,
|
||||
EnumType: EnumType{Name: "enum", Values: []string{"a", "b"}},
|
||||
},
|
||||
},
|
||||
}
|
||||
@ -128,9 +128,9 @@ func TestField_ValidateValue(t *testing.T) {
|
||||
{
|
||||
name: "valid enum",
|
||||
field: Field{
|
||||
Name: "field1",
|
||||
Kind: EnumKind,
|
||||
EnumDefinition: EnumDefinition{Name: "enum", Values: []string{"a", "b"}},
|
||||
Name: "field1",
|
||||
Kind: EnumKind,
|
||||
EnumType: EnumType{Name: "enum", Values: []string{"a", "b"}},
|
||||
},
|
||||
value: "a",
|
||||
errContains: "",
|
||||
@ -138,9 +138,9 @@ func TestField_ValidateValue(t *testing.T) {
|
||||
{
|
||||
name: "invalid enum",
|
||||
field: Field{
|
||||
Name: "field1",
|
||||
Kind: EnumKind,
|
||||
EnumDefinition: EnumDefinition{Name: "enum", Values: []string{"a", "b"}},
|
||||
Name: "field1",
|
||||
Kind: EnumKind,
|
||||
EnumType: EnumType{Name: "enum", Values: []string{"a", "b"}},
|
||||
},
|
||||
value: "c",
|
||||
errContains: "not a valid enum value",
|
||||
|
||||
@ -78,7 +78,7 @@ const (
|
||||
AddressKind
|
||||
|
||||
// EnumKind is an enum type and values of this type must be of the go type string.
|
||||
// Fields of this type are expected to set the EnumDefinition field in the field definition to the enum
|
||||
// Fields of this type are expected to set the EnumType field in the field definition to the enum
|
||||
// definition.
|
||||
EnumKind
|
||||
|
||||
|
||||
@ -31,7 +31,7 @@ func NewModuleSchema(objectTypes []ObjectType) (ModuleSchema, error) {
|
||||
}
|
||||
|
||||
func addEnumType(types map[string]Type, field Field) error {
|
||||
enumDef := field.EnumDefinition
|
||||
enumDef := field.EnumType
|
||||
if enumDef.Name == "" {
|
||||
return nil
|
||||
}
|
||||
@ -42,7 +42,7 @@ func addEnumType(types map[string]Type, field Field) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
existingEnum, ok := existing.(EnumDefinition)
|
||||
existingEnum, ok := existing.(EnumType)
|
||||
if !ok {
|
||||
return fmt.Errorf("enum %q already exists as a different non-enum type", enumDef.Name)
|
||||
}
|
||||
@ -119,3 +119,25 @@ func (s ModuleSchema) Types(f func(Type) bool) {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ObjectTypes iterators over all the object types in the schema in alphabetical order.
|
||||
func (s ModuleSchema) ObjectTypes(f func(ObjectType) bool) {
|
||||
s.Types(func(t Type) bool {
|
||||
objTyp, ok := t.(ObjectType)
|
||||
if ok {
|
||||
return f(objTyp)
|
||||
}
|
||||
return true
|
||||
})
|
||||
}
|
||||
|
||||
// EnumTypes iterators over all the enum types in the schema in alphabetical order.
|
||||
func (s ModuleSchema) EnumTypes(f func(EnumType) bool) {
|
||||
s.Types(func(t Type) bool {
|
||||
enumType, ok := t.(EnumType)
|
||||
if ok {
|
||||
return f(enumType)
|
||||
}
|
||||
return true
|
||||
})
|
||||
}
|
||||
|
||||
@ -51,7 +51,7 @@ func TestModuleSchema_Validate(t *testing.T) {
|
||||
{
|
||||
Name: "k",
|
||||
Kind: EnumKind,
|
||||
EnumDefinition: EnumDefinition{
|
||||
EnumType: EnumType{
|
||||
Name: "enum1",
|
||||
Values: []string{"a", "b"},
|
||||
},
|
||||
@ -61,7 +61,7 @@ func TestModuleSchema_Validate(t *testing.T) {
|
||||
{
|
||||
Name: "v",
|
||||
Kind: EnumKind,
|
||||
EnumDefinition: EnumDefinition{
|
||||
EnumType: EnumType{
|
||||
Name: "enum1",
|
||||
Values: []string{"a", "b", "c"},
|
||||
},
|
||||
@ -80,7 +80,7 @@ func TestModuleSchema_Validate(t *testing.T) {
|
||||
{
|
||||
Name: "k",
|
||||
Kind: EnumKind,
|
||||
EnumDefinition: EnumDefinition{
|
||||
EnumType: EnumType{
|
||||
Name: "enum1",
|
||||
Values: []string{"a", "b"},
|
||||
},
|
||||
@ -93,7 +93,7 @@ func TestModuleSchema_Validate(t *testing.T) {
|
||||
{
|
||||
Name: "k",
|
||||
Kind: EnumKind,
|
||||
EnumDefinition: EnumDefinition{
|
||||
EnumType: EnumType{
|
||||
Name: "enum1",
|
||||
Values: []string{"a", "c"},
|
||||
},
|
||||
@ -111,7 +111,7 @@ func TestModuleSchema_Validate(t *testing.T) {
|
||||
{
|
||||
Name: "k",
|
||||
Kind: EnumKind,
|
||||
EnumDefinition: EnumDefinition{
|
||||
EnumType: EnumType{
|
||||
Name: "enum1",
|
||||
Values: []string{"a", "b"},
|
||||
},
|
||||
@ -124,7 +124,7 @@ func TestModuleSchema_Validate(t *testing.T) {
|
||||
{
|
||||
Name: "k",
|
||||
Kind: EnumKind,
|
||||
EnumDefinition: EnumDefinition{
|
||||
EnumType: EnumType{
|
||||
Name: "enum1",
|
||||
Values: []string{"a", "b"},
|
||||
},
|
||||
@ -141,7 +141,7 @@ func TestModuleSchema_Validate(t *testing.T) {
|
||||
{
|
||||
Name: "field1",
|
||||
Kind: EnumKind,
|
||||
EnumDefinition: EnumDefinition{
|
||||
EnumType: EnumType{
|
||||
Name: "type1",
|
||||
Values: []string{"a", "b"},
|
||||
},
|
||||
@ -179,7 +179,7 @@ func TestModuleSchema_ValidateObjectUpdate(t *testing.T) {
|
||||
}{
|
||||
{
|
||||
name: "valid object update",
|
||||
moduleSchema: RequireNewModuleSchema(t, []ObjectType{
|
||||
moduleSchema: requireModuleSchema(t, []ObjectType{
|
||||
{
|
||||
Name: "object1",
|
||||
KeyFields: []Field{
|
||||
@ -199,7 +199,7 @@ func TestModuleSchema_ValidateObjectUpdate(t *testing.T) {
|
||||
},
|
||||
{
|
||||
name: "object type not found",
|
||||
moduleSchema: RequireNewModuleSchema(t, []ObjectType{
|
||||
moduleSchema: requireModuleSchema(t, []ObjectType{
|
||||
{
|
||||
Name: "object1",
|
||||
KeyFields: []Field{
|
||||
@ -219,14 +219,14 @@ func TestModuleSchema_ValidateObjectUpdate(t *testing.T) {
|
||||
},
|
||||
{
|
||||
name: "type name refers to an enum",
|
||||
moduleSchema: RequireNewModuleSchema(t, []ObjectType{
|
||||
moduleSchema: requireModuleSchema(t, []ObjectType{
|
||||
{
|
||||
Name: "obj1",
|
||||
KeyFields: []Field{
|
||||
{
|
||||
Name: "field1",
|
||||
Kind: EnumKind,
|
||||
EnumDefinition: EnumDefinition{
|
||||
EnumType: EnumType{
|
||||
Name: "enum1",
|
||||
Values: []string{"a", "b"},
|
||||
},
|
||||
@ -258,7 +258,7 @@ func TestModuleSchema_ValidateObjectUpdate(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func RequireNewModuleSchema(t *testing.T, objectTypes []ObjectType) ModuleSchema {
|
||||
func requireModuleSchema(t *testing.T, objectTypes []ObjectType) ModuleSchema {
|
||||
t.Helper()
|
||||
moduleSchema, err := NewModuleSchema(objectTypes)
|
||||
if err != nil {
|
||||
@ -268,7 +268,7 @@ func RequireNewModuleSchema(t *testing.T, objectTypes []ObjectType) ModuleSchema
|
||||
}
|
||||
|
||||
func TestModuleSchema_LookupType(t *testing.T) {
|
||||
moduleSchema := RequireNewModuleSchema(t, []ObjectType{
|
||||
moduleSchema := requireModuleSchema(t, []ObjectType{
|
||||
{
|
||||
Name: "object1",
|
||||
KeyFields: []Field{
|
||||
@ -295,14 +295,18 @@ func TestModuleSchema_LookupType(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestModuleSchema_ScanTypes(t *testing.T) {
|
||||
moduleSchema := RequireNewModuleSchema(t, []ObjectType{
|
||||
func exampleSchema(t *testing.T) ModuleSchema {
|
||||
return requireModuleSchema(t, []ObjectType{
|
||||
{
|
||||
Name: "object1",
|
||||
KeyFields: []Field{
|
||||
{
|
||||
Name: "field1",
|
||||
Kind: StringKind,
|
||||
Kind: EnumKind,
|
||||
EnumType: EnumType{
|
||||
Name: "enum2",
|
||||
Values: []string{"d", "e", "f"},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
@ -311,40 +315,94 @@ func TestModuleSchema_ScanTypes(t *testing.T) {
|
||||
KeyFields: []Field{
|
||||
{
|
||||
Name: "field1",
|
||||
Kind: StringKind,
|
||||
Kind: EnumKind,
|
||||
EnumType: EnumType{
|
||||
Name: "enum1",
|
||||
Values: []string{"a", "b", "c"},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
var objectTypeNames []string
|
||||
func TestModuleSchema_Types(t *testing.T) {
|
||||
moduleSchema := exampleSchema(t)
|
||||
|
||||
var typeNames []string
|
||||
moduleSchema.Types(func(typ Type) bool {
|
||||
objectType, ok := typ.(ObjectType)
|
||||
if !ok {
|
||||
t.Fatalf("expected object type, got %T", typ)
|
||||
}
|
||||
objectTypeNames = append(objectTypeNames, objectType.Name)
|
||||
typeNames = append(typeNames, typ.TypeName())
|
||||
return true
|
||||
})
|
||||
|
||||
expected := []string{"enum1", "enum2", "object1", "object2"}
|
||||
if !reflect.DeepEqual(typeNames, expected) {
|
||||
t.Fatalf("expected %v, got %v", expected, typeNames)
|
||||
}
|
||||
|
||||
typeNames = nil
|
||||
// scan just the first type and return false
|
||||
moduleSchema.Types(func(typ Type) bool {
|
||||
typeNames = append(typeNames, typ.TypeName())
|
||||
return false
|
||||
})
|
||||
|
||||
expected = []string{"enum1"}
|
||||
if !reflect.DeepEqual(typeNames, expected) {
|
||||
t.Fatalf("expected %v, got %v", expected, typeNames)
|
||||
}
|
||||
}
|
||||
|
||||
func TestModuleSchema_ObjectTypes(t *testing.T) {
|
||||
moduleSchema := exampleSchema(t)
|
||||
|
||||
var typeNames []string
|
||||
moduleSchema.ObjectTypes(func(typ ObjectType) bool {
|
||||
typeNames = append(typeNames, typ.Name)
|
||||
return true
|
||||
})
|
||||
|
||||
expected := []string{"object1", "object2"}
|
||||
if !reflect.DeepEqual(objectTypeNames, expected) {
|
||||
t.Fatalf("expected object type names %v, got %v", expected, objectTypeNames)
|
||||
if !reflect.DeepEqual(typeNames, expected) {
|
||||
t.Fatalf("expected %v, got %v", expected, typeNames)
|
||||
}
|
||||
|
||||
objectTypeNames = nil
|
||||
typeNames = nil
|
||||
// scan just the first type and return false
|
||||
moduleSchema.Types(func(typ Type) bool {
|
||||
objectType, ok := typ.(ObjectType)
|
||||
if !ok {
|
||||
t.Fatalf("expected object type, got %T", typ)
|
||||
}
|
||||
objectTypeNames = append(objectTypeNames, objectType.Name)
|
||||
moduleSchema.ObjectTypes(func(typ ObjectType) bool {
|
||||
typeNames = append(typeNames, typ.Name)
|
||||
return false
|
||||
})
|
||||
|
||||
expected = []string{"object1"}
|
||||
if !reflect.DeepEqual(objectTypeNames, expected) {
|
||||
t.Fatalf("expected object type names %v, got %v", expected, objectTypeNames)
|
||||
if !reflect.DeepEqual(typeNames, expected) {
|
||||
t.Fatalf("expected %v, got %v", expected, typeNames)
|
||||
}
|
||||
}
|
||||
|
||||
func TestModuleSchema_EnumTypes(t *testing.T) {
|
||||
moduleSchema := exampleSchema(t)
|
||||
|
||||
var typeNames []string
|
||||
moduleSchema.EnumTypes(func(typ EnumType) bool {
|
||||
typeNames = append(typeNames, typ.Name)
|
||||
return true
|
||||
})
|
||||
|
||||
expected := []string{"enum1", "enum2"}
|
||||
if !reflect.DeepEqual(typeNames, expected) {
|
||||
t.Fatalf("expected %v, got %v", expected, typeNames)
|
||||
}
|
||||
|
||||
typeNames = nil
|
||||
// scan just the first type and return false
|
||||
moduleSchema.EnumTypes(func(typ EnumType) bool {
|
||||
typeNames = append(typeNames, typ.Name)
|
||||
return false
|
||||
})
|
||||
|
||||
expected = []string{"enum1"}
|
||||
if !reflect.DeepEqual(typeNames, expected) {
|
||||
t.Fatalf("expected %v, got %v", expected, typeNames)
|
||||
}
|
||||
}
|
||||
|
||||
@ -27,6 +27,11 @@ type ObjectType struct {
|
||||
RetainDeletions bool
|
||||
}
|
||||
|
||||
// TypeName implements the Type interface.
|
||||
func (o ObjectType) TypeName() string {
|
||||
return o.Name
|
||||
}
|
||||
|
||||
func (ObjectType) isType() {}
|
||||
|
||||
// Validate validates the object type.
|
||||
|
||||
@ -170,7 +170,7 @@ func TestObjectType_Validate(t *testing.T) {
|
||||
{
|
||||
Name: "key",
|
||||
Kind: EnumKind,
|
||||
EnumDefinition: EnumDefinition{
|
||||
EnumType: EnumType{
|
||||
Name: "enum1",
|
||||
Values: []string{"a", "b"},
|
||||
},
|
||||
@ -180,7 +180,7 @@ func TestObjectType_Validate(t *testing.T) {
|
||||
{
|
||||
Name: "value",
|
||||
Kind: EnumKind,
|
||||
EnumDefinition: EnumDefinition{
|
||||
EnumType: EnumType{
|
||||
Name: "enum1",
|
||||
Values: []string{"c", "b"},
|
||||
},
|
||||
|
||||
@ -1,7 +1,10 @@
|
||||
package schema
|
||||
|
||||
// Type is an interface that all types in the schema implement.
|
||||
// Currently these are ObjectType and EnumDefinition.
|
||||
// Currently these are ObjectType and EnumType.
|
||||
type Type interface {
|
||||
// TypeName returns the type's name.
|
||||
TypeName() string
|
||||
|
||||
isType()
|
||||
}
|
||||
|
||||
Loading…
Reference in New Issue
Block a user