Skip to content

lvyahui8/goenum

Repository files navigation

Go enumeration template implementation

License Go Report Card codecov

中文文档

How to define and use enumerations?

Simply embed goenum.Enum into a struct to define an enumeration type and automatically obtain a set of instance methods and utility functions.

go get github.com/lvyahui8/goenum
import "github.com/lvyahui8/goenum"

// Declaring an enumeration type
type State struct {
    goenum.Enum
}

// Defines a set of enumeration instances
var (
    Created = goenum.NewEnum[State]("Created")
    Running = goenum.NewEnum[State]("Running")
    Success = goenum.NewEnum[State]("Success")
)

// Usage
Created.Name() // string "Created"
Created.Ordinal() // int 0
Created.Compare(Running) //  < 0 : Created < Running
Created.String() // Created
json.Marshal(Created) // \"Created\"
IsValidEnum[State]("Created") // true
s,valid := goenum.ValueOf[State]("Created") // s: Created(struct instance) ,valid = true
Created.Equals(s) // true
s,valid := goenum.ValueOf[State]("cReaTed") //  s: Created(struct instance) ,valid = true
Created.Equals(s) // true
goenum.Values[State]() // equals []State{Created,Running,Success}
Size[State]() // = 3

More Examples examples

Instance Methods

type EnumDefinition interface {
    fmt.Stringer
    json.Marshaler
    encoding.TextMarshaler
    // Name The name of the enumeration. The enumeration names of the same Type must be unique.
    Name() string
    // Equals Compare with another enumeration. Only return true if both Type and Name are the same
    Equals(other EnumDefinition) bool
    // Type String representation of enumeration type
    Type() string
    // Ordinal Get the ordinal of the enumeration, starting from zero and increasing in declared order.
    Ordinal() int
    // Compare -Compare with the ordinal value of another enumeration
    Compare(other EnumDefinition) int
}

Utility functions

  • ValueOf: Find an enumeration instance based on the string, and return a zero value if not found
  • ValueOfIgnoreCase: Ignoring case to obtain enumeration instances.
  • Values: Return all enumeration instances. The returned slice are sorted by ordinal
  • Size: Number of instances of specified enumeration type
  • GetEnumMap: Get all enumeration instances of the specified type.
  • EnumNames: Get the names of a batch of enumerations.
  • GetEnums: Obtain a batch of enumeration instances based on the enumeration name list
  • IsValidEnum: Determine if the incoming string is a valid enumeration
func TestHelpers(t *testing.T) {
	t.Run("NewEnum", func(t *testing.T) {
		defer func() {
			err := recover()
			require.NotNil(t, err)
			require.Equal(t, "Enum must be unique", err)
		}()
		_ = goenum.NewEnum[Role]("Owner")
	})
	t.Run("ValueOf", func(t *testing.T) {
		r, valid := goenum.ValueOf[Role]("Owner")
		require.True(t, valid)
		require.True(t, Owner.Equals(r))
		r, valid = goenum.ValueOf[Role]("Owner")
		require.True(t, valid)
		require.False(t, Developer.Equals(r))
	})
	t.Run("ValueOfIgnoreCase", func(t *testing.T) {
		r, valid := goenum.ValueOfIgnoreCase[Role]("oWnEr")
		require.True(t, valid)
		require.True(t, Owner.Equals(r))
		r, valid = goenum.ValueOfIgnoreCase[Role]("oWnEr")
		require.True(t, valid)
		require.False(t, Reporter.Equals(r))
	})
	t.Run("Values", func(t *testing.T) {
		require.True(t, reflect.DeepEqual([]Role{Reporter, Developer, Owner}, goenum.Values[Role]()))
	})
	t.Run("GetEnumMap", func(t *testing.T) {
		enumMap := goenum.GetEnumMap[Role]()
		require.True(t, len(enumMap) == 3)
		role, exist := enumMap["Owner"]
		require.True(t, exist)
		require.True(t, role.Equals(Owner))
	})
	t.Run("EnumNames", func(t *testing.T) {
		require.True(t, reflect.DeepEqual([]string{"Owner", "Developer"}, goenum.EnumNames(Owner, Developer)))
	})
	t.Run("GetEnums", func(t *testing.T) {
		res, valid := goenum.GetEnums[Role]("Owner", "Developer")
		require.True(t, valid)
		require.True(t, reflect.DeepEqual([]Role{Owner, Developer}, res))
		_, valid = goenum.GetEnums[Role]("a", "b")
		require.False(t, valid)
	})
	t.Run("IsValidEnum", func(t *testing.T) {
		require.True(t, goenum.IsValidEnum[Role]("Owner"))
		require.False(t, goenum.IsValidEnum[Role]("Test"))
	})
}

More features

Complex enumeration initialization

The second parameter in the NewEnum function, if a Src is passed in, NewEnum will use the passed object to construct an enumeration instance.

example code gitlab_role_perms

type Module struct {
	goenum.Enum
	perms    []Permission
	basePath string
}

func (m Module) GetPerms() []Permission {
	return m.perms
}

func (m Module) BasePath() string {
	return m.basePath
}

// 定义模块
var (
	Issues        = goenum.NewEnum[Module]("Issues", Module{perms: []Permission{AddLabels, AddTopic}, basePath: "/issues/"})
	MergeRequests = goenum.NewEnum[Module]("MergeRequests", Module{perms: []Permission{ViewMergeRequest, ApproveMergeRequest, DeleteMergeRequest}, basePath: "/merge/"})
)

JSON serialization and deserialization support for enumerating instances

JSON serialization is already supported by default. Restricted by the implementation of the go JSON library, enumeration classes are required to implement JSON Unmarshaler interface to realize deserialization. Call Unmarshal tool function in the interface to obtain enumeration instances.

type Member struct {
	Roles []Role
}

type Role struct {
	goenum.Enum
	perms []Permission
}

func (r *Role) UnmarshalJSON(data []byte) (err error) {
	role, err := goenum.Unmarshal[Role](data)
	if err == nil {
		*r = role
	}
	return
}

t.Run("jsonMarshal", func(t *testing.T) {
    bytes, err := json.Marshal(Developer)
    require.Nil(t, err)
    require.Equal(t, "\"Developer\"", string(bytes))
    member := Member{Roles: []Role{Reporter, Owner}}
    bytes, err = json.Marshal(member)
    require.Nil(t, err)
    require.Equal(t, "{\"Roles\":[\"Reporter\",\"Owner\"]}", string(bytes))
})
t.Run("jsonUnmarshal", func(t *testing.T) {
    newMember := Member{}
    err := json.Unmarshal([]byte("{\"Roles\":[\"Reporter\",\"Owner\"]}"), &newMember)
    require.Nil(t, err)
    require.True(t, reflect.DeepEqual([]Role{Reporter, Owner}, newMember.Roles))
})

EnumSet

api声明

// EnumSet Enumerate sets. Usually used for high-performance lookup when
// there are many instances of a certain type of enumeration.
type EnumSet[E EnumDefinition] interface {
    fmt.Stringer
    json.Marshaler
    // Add -Add an element to Set. If successful, return true. If it already exists, return false
    Add(e E) bool
    // AddRange According to the ordinal of the enumeration,
    // add a continuous section of the enumeration and
    // return the actual number added (excluding those that already exist)
    AddRange(begin, end E) int
    // Remove Delete element. If the deletion is successful, return true.
    // If the element does not exist, return false
    Remove(e E) bool
    // RemoveRange  According to the ordinal of the enumeration,
    // continuously delete a segment of the enumeration and
    // return the actual number of deletions  (excluding those that non-exist)
    RemoveRange(begin, end E) int
    // IsEmpty Is Set empty
    IsEmpty() bool
    // Clear -Clear set
    Clear()
    // Len The current number of enumeration instances within the Set
    Len() int
    // Contains Does it contain the specified enumeration?
    // Returns false if there is only one that does not exist in the Set
    Contains(enums ...E) bool
    // ContainsAll  Determine if it contains another enumSet (subset relationship)
    ContainsAll(set EnumSet[E]) bool
    // Equals Determine if two EnumSets are the same
    Equals(set EnumSet[E]) bool
    // Each Set iteration method, if f method returns false, abort iteration
    Each(f func(e E) bool)
    // Names Returns the Name representation of an existing enumeration instance in the set
    Names() []string
    // Clone Deep copy to obtain a new set
    Clone() EnumSet[E]
}

EnumSet usage

stmtSet := NewUnsafeEnumSet[Statement]()

stmtSet.Add(Decl)
stmtSet.IsEmpty()
stmtSet.Contains(Decl)
stmtSet.Len() == 1
stmtSet.AddRange(Comm, Range)
stmtSet.Remove(Decl)

Example code enum_set_test

ValueOf Performance

Don't worry about any performance issues, reflection calls are mostly only used in NewEnum methods, and other methods will try to avoid reflection calls as much as possible.

goos: linux
goarch: arm64
pkg: github.com/lvyahui8/goenum/internal
BenchmarkValueOf/ValueOf-4             1000000000             0.0002492 ns/op           0 B/op           0 allocs/op
BenchmarkValueOf/ValueOf-4             1000000000             0.0002966 ns/op           0 B/op           0 allocs/op
BenchmarkValueOf/ValueOf-4             1000000000             0.0002713 ns/op           0 B/op           0 allocs/op
BenchmarkValueOf/ValueOfIgnoreCase-4             1000000000             0.002228 ns/op           0 B/op           0 allocs/op
BenchmarkValueOf/ValueOfIgnoreCase-4             1000000000             0.002611 ns/op           0 B/op           0 allocs/op
BenchmarkValueOf/ValueOfIgnoreCase-4             1000000000             0.002606 ns/op           0 B/op           0 allocs/op
PASS
ok      github.com/lvyahui8/goenum/internal    0.097s