From 339e26ea8f3a1f2c81ae2fa8064a73f24dd96883 Mon Sep 17 00:00:00 2001 From: Aaron Craelius Date: Wed, 24 Jul 2024 15:42:47 +0200 Subject: [PATCH] feat(schema)!: add more range methods, rename EnumDefinition -> EnumType (#21043) --- schema/enum.go | 15 +++-- schema/enum_test.go | 14 ++-- schema/field.go | 12 ++-- schema/field_test.go | 24 +++---- schema/kind.go | 2 +- schema/module_schema.go | 26 +++++++- schema/module_schema_test.go | 126 +++++++++++++++++++++++++---------- schema/object_type.go | 5 ++ schema/object_type_test.go | 4 +- schema/type.go | 5 +- 10 files changed, 163 insertions(+), 70 deletions(-) diff --git a/schema/enum.go b/schema/enum.go index 5afb0ecbd0..5ceb435313 100644 --- a/schema/enum.go +++ b/schema/enum.go @@ -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 diff --git a/schema/enum_test.go b/schema/enum_test.go index 435449d0c5..51881f29ff 100644 --- a/schema/enum_test.go +++ b/schema/enum_test.go @@ -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"}, } diff --git a/schema/field.go b/schema/field.go index a11cc75668..bd3e3997e1 100644 --- a/schema/field.go +++ b/schema/field.go @@ -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 diff --git a/schema/field_test.go b/schema/field_test.go index ea839ece08..5fd7d8015c 100644 --- a/schema/field_test.go +++ b/schema/field_test.go @@ -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", diff --git a/schema/kind.go b/schema/kind.go index c28ce34af0..1cdffc7b71 100644 --- a/schema/kind.go +++ b/schema/kind.go @@ -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 diff --git a/schema/module_schema.go b/schema/module_schema.go index b98744ea81..f90a44c192 100644 --- a/schema/module_schema.go +++ b/schema/module_schema.go @@ -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 + }) +} diff --git a/schema/module_schema_test.go b/schema/module_schema_test.go index 189c7f3b99..ebd44e6536 100644 --- a/schema/module_schema_test.go +++ b/schema/module_schema_test.go @@ -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) } } diff --git a/schema/object_type.go b/schema/object_type.go index 9dd3742a55..379d7d4a83 100644 --- a/schema/object_type.go +++ b/schema/object_type.go @@ -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. diff --git a/schema/object_type_test.go b/schema/object_type_test.go index 0a78a371aa..68a85111b7 100644 --- a/schema/object_type_test.go +++ b/schema/object_type_test.go @@ -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"}, }, diff --git a/schema/type.go b/schema/type.go index 1b3ef06577..75155de868 100644 --- a/schema/type.go +++ b/schema/type.go @@ -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() }