Skip to content

go-enumr is a rich enum implementation in golang

License

Notifications You must be signed in to change notification settings

jmfrees/go-enumr

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

18 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

go-enumr

Go Report Card Go Reference CI

Rich enums for Go.

go-enumr is a code generation tool for creating Rich Enums in Go. The name is a nod to both Rich enums and the Go convention of agent nouns (like Stringer)—it's the tool that makes your types work as enums.

Unlike standard Go enums (which are just integers), go-enumr allows you to use structs as enums. This enables you to bundle associated data—like labels, database IDs, configuration values, or color codes—directly with your enum identity.

Why Rich Enums?

A Rich Enum is an enumerated type where each variant carries its own immutable data. Instead of scattering your logic across multiple switch statements or lookup maps, you encapsulate the data within the enum value itself.

The Problem with Standard Enums

Standard Go enums are great for simple flags, but they fall short when you need associated data:

// Standard Go Enum
type Status int
const (
    Pending Status = iota
    Active
)
// Problem: How do I get the string label? The database ID? The color for the UI?
// You usually end up writing massive switch statements or lookup maps.

Rich Enums solve this by keeping data together:

// go-enumr Rich Enum
type Status struct {
    ID    int
    Label string
    Color string
}

var (
    Pending = Status{0, "Pending", "#FFA500"}
    Active  = Status{1, "Active",  "#00FF00"}
)

go-enumr automates the boilerplate needed to make these structs behave like proper enums, generating String(), MarshalText(), UnmarshalText(), and helper functions like Values() and Parse().

When to use go-enumr

Use go-enumr when:

  • Your enum variants have associated data (labels, config, database IDs).
  • You want to keep that data co-located with the definition.
  • You need to look up enum instances by their fields (e.g., find the status with ID 5).

Use stringer or go-enum when:

  • You just need a simple list of names (nominal enums).
  • You don't have any extra data attached to your enum values.
  • You are fine with using iota or simple underlying types (int/string).

Installation

Go 1.24+ (Recommended)

You can track go-enumr as a tool dependency in your go.mod file:

go get -tool github.com/jmfrees/go-enumr/cmd/enumr

Then run it via go tool:

go tool enumr -type=Method

Or in your //go:generate directive:

//go:generate go tool enumr -type=Method

Go install (Global)

go install github.com/jmfrees/go-enumr/cmd/enumr@latest

Usage

There are two ways to use go-enumr:

  1. Directive Mode (Recommended): Define your enum values in comments.
  2. Manual Mode: Define your enum values in a var block.

1. Directive Mode (Recommended)

Use //enumr:<Name> comments to define your enum instances. The tool will generate the var block for you.

package payment

//go:generate enumr -type=Method

//enumr:CreditCard   Code:CC   Description:"Credit Card"  IsCredit:true
//enumr:PayPal       Code:PP   Description:PayPal
//enumr:BankTransfer Code:BT   Description:"Bank Transfer"
type Method struct {
    Code        string
    Description string
    IsCredit    bool
}

Run the generator:

go generate ./...

This creates method_enum.go containing:

var (
    CreditCard   = Method{Code: "CC", Description: "Credit Card", IsCredit: true}
    PayPal       = Method{Code: "PP", Description: "PayPal"}
    BankTransfer = Method{Code: "BT", Description: "Bank Transfer"}
)

// ... generated methods ...

Syntax Rules:

  • Key:Value sets a field (e.g., Code:CC).
  • Key:"Value with spaces" sets a string field with spaces.
  • Key:true sets a boolean field (e.g., IsCredit:true).
  • Key:"[]string{\"a\", \"b\"}" sets complex types (slices, structs) by quoting the Go syntax.
  • Fields not specified default to their zero value.

2. Manual Mode

If you need complex initialization (e.g., function calls, external imports) or want to document individual instances, you can define the variables yourself.

When to use Manual Mode:

  • You need to use functions like time.Date().
  • You need to import other packages.
  • You want to add godoc comments to specific enum instances.
package payment

//go:generate enumr -type=Method

type Method struct {
    Code string
    Created time.Time
}

var (
    CreditCard = Method{"CC", time.Date(2024, 1, 1, 0, 0, 0, 0, time.UTC)}
)

go-enumr will detect the existing var block and generate the helper methods for it.

Generated Code

The tool generates the following for your type:

  • func (t Type) String() string: Returns the enum name (e.g., "credit_card") or the value of the field specified by -marshal-field.
  • func (t Type) MarshalText() ([]byte, error): Implements encoding.TextMarshaler.
  • func (t *Type) UnmarshalText([]byte) error: Implements encoding.TextUnmarshaler. Matches the string representation exactly.
  • func TypeValues() []Type: Returns a slice of all enum instances.
  • func ParseType(s string) (Type, error): Helper to parse a string into an enum instance.

CLI Options

  • -type: (Required) Comma-separated list of type names to generate code for.
  • -marshal-field: (Optional) The name of the struct field to use for marshaling (e.g., Code). If not provided, the enum instance name is used (transformed by -format).
  • -format: (Optional) Casing format for the string representation (ignored if -marshal-field is used).
    • (empty) (default): Preserves the original casing.
    • snake_case
    • camelCase
    • PascalCase
    • SNAKE_CASE
    • Title Case
  • -output: (Optional) Output file name or directory. Defaults to <type>_enum.go in the package directory.
  • -zero: (Optional) Allow the zero value (empty string) to be parsed as a valid enum value. Useful for optional fields that default to the zero value.

Best Practices

Since Go structs cannot be const, these enums are defined as var. While technically mutable, the convention is to treat them as immutable constants.

Immutability with Private Fields

To more strictly enforce immutability, the recommended pattern is to use private fields in your struct definition and expose data via public getter methods.

//enumr:Free Price:0.0
//enumr:Pro  Price:29.99
type Plan struct {
    price float64 // private field
}

// Public getter prevents external modification
func (p Plan) Price() float64 {
    return p.price
}

This ensures that consumers of your package can read the data but cannot modify the enum fields directly.

Inspiration

This project is inspired by the excellent work of:

  • stringer: The official Go tool for generating String() methods.
  • go-enum: A powerful enum generator that supports parsing from comments.

go-enumr builds on these ideas but focuses specifically on the "Rich Enum" pattern, where the enum value itself is a struct containing data, rather than just an integer or string.

Contributing

Contributions are welcome! Please feel free to submit a Pull Request.

  1. Fork the repository.
  2. Create your feature branch (git checkout -b feature/AmazingFeature).
  3. Commit your changes (git commit -m 'Add some AmazingFeature').
  4. Push to the branch (git push origin feature/AmazingFeature).
  5. Open a Pull Request.

License

MIT

About

go-enumr is a rich enum implementation in golang

Resources

License

Stars

Watchers

Forks

Languages