goenums

command module
v0.4.3 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Jun 30, 2025 License: MIT Imports: 18 Imported by: 0

README

goenums

build Go Report Card

goenums addresses Go's lack of native enum support by generating comprehensive, type-safe enum implementations from simple constant declarations. Transform basic iota based constants into feature-rich enums with string conversion, validation, JSON handling, database integration, and more.

Installation

go install github.com/zarldev/goenums@latest

Documentation

Documentation is available at https://zarldev.github.io/goenums.

Table of Contents

Key Features

  • Type Safety: Wrapper types prevent accidental misuse of enum values
  • String Conversion: Automatic string representation and parsing
  • JSON Support: Built-in marshaling and unmarshaling
  • YAML Support: Built-in YAML marshaling and unmarshaling
  • Database Integration: SQL Scanner and Valuer implementations
  • Text/Binary Marshaling: Support for encoding.TextMarshaler/TextUnmarshaler and BinaryMarshaler/BinaryUnmarshaler
  • Numeric Parsing: Parse enums from various numeric types (int, float, etc.)
  • Validation: Methods to check for valid enum values
  • Iteration: Modern Go 1.23+ iteration support with legacy fallback
  • Extensibility: Add custom fields to enums via comments
  • Exhaustive Handling: Helper functions to ensure you handle all enum values
  • Alias Support: Alternative enum names via comment syntax
  • Zero Dependencies: Completely dependency-free, using only the Go standard library

Usage

$ goenums -h
   ____ _____  ___  ____  __  ______ ___  _____
  / __ '/ __ \/ _ \/ __ \/ / / / __ '__ \/ ___/
 / /_/ / /_/ /  __/ / / / /_/ / / / / / (__  ) 
 \__, /\____/\___/_/ /_/\__,_/_/ /_/ /_/____/  
/____/
Usage: goenums [options] file.go[,file2.go,...]
Options:
  -c
  -constraints
    	Specify whether to generate the float and integer constraints or import 'golang.org/x/exp/constraints' (default: false - imports)
  -f
  -failfast
    	Enable failfast mode - fail on generation of invalid enum while parsing (default: false)
  -h
  -help
    	Print help information
  -i
  -insensitive
    	Generate case insensitive string parsing (default: false)
  -l
  -legacy
    	Generate legacy code without Go 1.23+ iterator support (default: false)
  -o string
  -output string
    	Specify the output format (default: go)
  -v
  -version
    	Print version information
  -vv
  -verbose
    	Enable verbose mode - prints out the generated code (default: false)

Features Expanded

Custom String Representations

Handle custom string representations defined in the comments of each enum. Support for enum strings with spaces is supported by adding the alternative name in double quotes:

Standard Name Comment

When the Alternative name does not contain spaces there is no need to add the double quotes.

type status int

//go:generate goenums -f -c status.go
const (
    unknown   status = iota // invalid UNKNOWN
    failed                  // FAILED
    passed                  // PASSED
    skipped                 // SKIPPED
    scheduled               // SCHEDULED
    running                 // RUNNING
    booked                  // BOOKED
)
Name Comment with spaces

When using Alternative names that contain spaces, the double quotes are required.

type order int

//go:generate goenums status.go
const (
	created     order = iota // "CREATED"
	approved                 // "APPROVED"
	processing               // "PROCESSING"
	readyToShip              // "READY TO SHIP"
	shipped                  // "SHIPPED TO CUSTOMER"
	delivered                // "DELIVERED TO CUSTOMER"
	cancelled                // "CANCELLED BY CUSTOMER"
	refunded                 // "REFUNDED TO CUSTOMER"
	closed                   // "CLOSED"
)

Extended Enum Types with Custom Fields

Add custom fields to your enums with type comments:

// Define fields in the type comment using one of three formats:
// 1. Space-separated: "Field Type,AnotherField Type"
// 2. Brackets: "Field[Type],AnotherField[Type]"
// 3. Parentheses: "Field(Type),AnotherField(Type)"

type planet int // Gravity float64,RadiusKm float64,MassKg float64,OrbitKm float64,OrbitDays float64,SurfacePressureBars float64,Moons int,Rings bool

//go:generate goenums planets.go
const (
	mercury planet = iota // Mercury 0.378,2439.7,3.3e23,57910000,88,0.0000000001,0,false
	venus                     // Venus 0.907,6051.8,4.87e24,108200000,225,92,0,false
	earth                     // Earth 1,6378.1,5.97e24,149600000,365,1,1,false
	mars                      // Mars 0.377,3389.5,6.42e23,227900000,687,0.01,2,false
	jupiter                   // Jupiter 2.36,69911,1.90e27,778600000,4333,20,4,true
	saturn                    // Saturn 0.916,58232,5.68e26,1433500000,10759,1,7,true
	uranus                    // Uranus 0.889,25362,8.68e25,2872500000,30687,1.3,13,true
	neptune                   // Neptune 1.12,24622,1.02e26,4495100000,60190,1.5,2,true
)

Then we can use the extended enum type:

earthWeight := 100.0
fmt.Printf("Weight on %s: %.2f kg\n", 
    solarsystem.Planets.MARS, 
    earthWeight * solarsystem.Planets.MARS.Gravity)

Case Insensitive String Parsing

Use the -i flag to enable case insensitive string parsing:

//go:generate goenums -i status.go

// Generated code will parse case insensitive strings. All
// of the below will validate and produce the 'PASSED' enum
status, err := validation.ParseStatus("PASSED")
if err != nil {
    fmt.Println("error:", err)
}
status, err := validation.ParseStatus("passed")
if err != nil {
    fmt.Println("error:", err)
}
status, err := validation.ParseStatus("Passed")
if err != nil {
    fmt.Println("error:", err)
}

JSON, Text, Binary, YAML, and Database Storage

The generated enum type also implements several common interfaces:

  • json.Marshaler and json.Unmarshaler
  • sql.Scanner and sql.Valuer
  • encoding.TextMarshaler and encoding.TextUnmarshaler
  • encoding.BinaryMarshaler and encoding.BinaryUnmarshaler

These interfaces are used to handle parsing for JSON, Text, Binary, and Database storage using the common standard library packages. As there is no standard library support for YAML, the generated YAML marshaling and unmarshaling methods are based on the * yaml.Marshaler and yaml.Unmarshaler interfaces from the goccy/go-yaml module.

Here is an example of the generated handling code:

// MarshalJSON implements the json.Marshaler interface for Status.
// It returns the JSON representation of the enum value as a byte slice.
func (p Status) MarshalJSON() ([]byte, error) {
	return []byte("\"" + p.String() + "\""), nil
}

// UnmarshalJSON implements the json.Unmarshaler interface for Status.
// It parses the JSON representation of the enum value from the byte slice.
// It returns an error if the input is not a valid JSON representation.
func (p *Status) UnmarshalJSON(b []byte) error {
	b = bytes.Trim(bytes.Trim(b, "\""), "\"")
	newp, err := ParseStatus(b)
	if err != nil {
		return err
	}
	*p = newp
	return nil
}

// MarshalText implements the encoding.TextMarshaler interface for Status.
// It returns the string representation of the enum value as a byte slice
func (p Status) MarshalText() ([]byte, error) {
	return []byte("\"" + p.String() + "\""), nil
}

// UnmarshalText implements the encoding.TextUnmarshaler interface for Status.
// It parses the string representation of the enum value from the byte slice.
// It returns an error if the byte slice does not contain a valid enum value.
func (p *Status) UnmarshalText(b []byte) error {
	newp, err := ParseStatus(b)
	if err != nil {
		return err
	}
	*p = newp
	return nil
}

// Scan implements the database/sql.Scanner interface for Status.
// It parses the string representation of the enum value from the database row.
// It returns an error if the row does not contain a valid enum value.
func (p *Status) Scan(value any) error {
	newp, err := ParseStatus(value)
	if err != nil {
		return err
	}
	*p = newp
	return nil
}

// Value implements the database/sql/driver.Valuer interface for Status.
// It returns the string representation of the enum value.
func (p Status) Value() (driver.Value, error) {
	return p.String(), nil
}

// MarshalBinary implements the encoding.BinaryMarshaler interface for Status.
// It returns the binary representation of the enum value as a byte slice.
func (p Status) MarshalBinary() ([]byte, error) {
	return []byte("\"" + p.String() + "\""), nil
}

// UnmarshalBinary implements the encoding.BinaryUnmarshaler interface for Status.
// It parses the binary representation of the enum value from the byte slice.
// It returns an error if the byte slice does not contain a valid enum value.
func (p *Status) UnmarshalBinary(b []byte) error {
	newp, err := ParseStatus(b)
	if err != nil {
		return err
	}
	*p = newp
	return nil
}

Numeric Parsing Support

The generated enums support parsing from various numeric types, automatically converting them to the appropriate enum value:

// Parse from different numeric types
status1, _ := validation.ParseStatus(1)        // int
status2, _ := validation.ParseStatus(int32(2)) // int32
status3, _ := validation.ParseStatus(3.0)      // float64
status4, _ := validation.ParseStatus(uint8(4)) // uint8

// All numeric types are supported: int, int8, int16, int32, int64,
// uint, uint8, uint16, uint32, uint64, float32, float64

The numeric parsing validates that:

  • Float values are whole numbers (no fractional part)
  • The numeric value corresponds to a valid enum position
  • Values are within the valid range of enum constants

Exhaustive Handling

Ensure you handle all enum values with the generated Exhaustive function:

// Process all enum values safely
// This is especially useful in tests to ensure all enum values are covered
validation.ExhaustiveStatuses(func(status validation.Status) {
    // Process each status exactly once
    switch status {
    case validation.Statuses.FAILED:
        handleFailed()
    case validation.Statuses.PASSED:
        handlePassed()
    // ... handle all other cases
    }
})

// We can also iterate over all enum values to do exhaustive calculations
weightKg := 100.0
solarsystem.ExhaustivePlanets(func(p solarsystem.Planet) {
	// calculate weight on each planet
	gravity := p.Gravity
	planetMass := weightKg * gravity
	fmt.Printf("Weight on %s is %fKg with gravity %f\n", p, planetMass, gravity)
})

Iterator Support (Go 1.23+)

By default, goenums generates modern iterator support using Go 1.23's range-over-func feature:

// Using Go 1.23+ iterator
for status := range validation.Statuses.All() {
    fmt.Printf("Status: %s\n", status)
}

When using the legacy mode, the function is still called All() but it returns a slice of the enums.

// Legacy mode (or with -l flag)
for _, status := range validation.Statuses.All() {
    fmt.Printf("Status: %s\n", status)
}

Failfast Mode / Strict Mode

You can enable failfast mode by using the -failfast flag. This will cause the generator to fail on the first invalid enum it encounters while parsing. In failfast mode, the generated code includes a specific error variable and wrapped error returns:

//go:generate goenums -f status.go

// Generated code includes a specific error variable:
var ErrParseStatus = errors.New("invalid input provided to parse to Status")

// And returns wrapped errors for invalid values:
status, err := validation.ParseStatus("INVALID_STATUS")
if err != nil {
    // err will be: invalid input provided to parse to Status: invalid value INVALID_STATUS
    fmt.Println("error:", err)
    
    // You can check for the specific error type:
    if errors.Is(err, validation.ErrParseStatus) {
        fmt.Println("This is a Status parsing error")
    }
}

Legacy Mode

You can enable legacy mode by using the -legacy flag. This will generate code that is compatible with Go versions before 1.23.

Verbose Mode

You can enable verbose mode by using the -verbose flag. This will print out the generated code to the console.

Constraints Mode

You can enable constraints mode by using the -constraints flag. This will generate local type constraints instead of importing golang.org/x/exp/constraints. This is useful if you want to avoid external dependencies.

//go:generate goenums -c status.go

// Generated code will include local constraint definitions:
type float interface {
    float32 | float64
}
type integer interface {
    int | int8 | int16 | int32 | int64 | uint | uint8 | uint16 | uint32 | uint64 | uintptr
}
type number interface {
    integer | float
}

Output Format

You can specify the output format by using the -output flag. The default is go.

Compile-time Validation

The generated code includes compile-time validation to ensure enum values remain consistent. If you modify the underlying enum constants, the compiler will detect changes and prompt you to regenerate the enum code:

// Compile-time check that all enum values are valid.
// This function is used to ensure that all enum values are defined and valid.
// It is called by the compiler to verify that the enum values are valid.
func _() {
    // An "invalid array index" compiler error signifies that the constant values have changed.
    // Re-run the goenums command to generate them again.
    // Does not identify newly added constant values unless order changes
    var x [7]struct{}
    _ = x[unknown]
    _ = x[failed-1]
    _ = x[passed-2]
    _ = x[skipped-3]
    _ = x[scheduled-4]
    _ = x[running-5]
    _ = x[booked-6]
}

This ensures that if you change the order or values of your enum constants, you'll get a compile error reminding you to regenerate the enum code.

Getting Started

Basic Example

goenums is designed to work seamlessly with Go's standard tooling, particularly with go:generate directives. This allows you to automatically regenerate your enum code whenever your source files change, integrating smoothly into your existing build process.

  1. Define your enum constant in a Go file:
package validation

type status int

//go:generate goenums -f -c status.go
const (
	unknown status = iota // invalid UNKNOWN
	failed                // FAILED
	passed                // PASSED
	skipped               // SKIPPED
	scheduled             // SCHEDULED
	running               // RUNNING
	booked                // BOOKED
)
  1. Run go generate ./... to generate the enum implementations.

  2. Use the generated status enums type in your code:

// Access enum constants safely
myStatus := validation.Statuses.PASSED

// Convert to string
fmt.Println(myStatus.String()) // "PASSED"

// Parse from various sources
input := "SKIPPED"
parsed, _ := validation.ParseStatus(input)

// Validate enum values
if !parsed.IsValid() {
    fmt.Println("Invalid status")
}

// JSON marshaling/unmarshaling works automatically
type Task struct {
    ID     int              `json:"id"`
    Status validation.Status `json:"status"`
}

Requirements

  • Go 1.23+ for iterator support (or use -l flag for legacy mode)

Examples

Input source go file:

package solarsystem

type planet int // Gravity float64,RadiusKm float64,MassKg float64,OrbitKm float64,OrbitDays float64,SurfacePressureBars float64,Moons int,Rings bool

//go:generate goenums planets.go
const (
	mercury planet = iota // Mercury 0.378,2439.7,3.3e23,57910000,88,0.0000000001,0,false
	venus                 // Venus 0.907,6051.8,4.87e24,108200000,225,92,0,false
	earth                 // Earth 1,6378.1,5.97e24,149600000,365,1,1,false
	mars                  // Mars 0.377,3389.5,6.42e23,227900000,687,0.01,2,false
	jupiter               // Jupiter 2.36,69911,1.90e27,778600000,4333,20,4,true
	saturn                // Saturn 0.916,58232,5.68e26,1433500000,10759,1,7,true
	uranus                // Uranus 0.889,25362,8.68e25,2872500000,30687,1.3,13,true
	neptune               // Neptune 1.12,24622,1.02e26,4495100000,60190,1.5,2,true
)

Produces a go output file called planets_enums.go with the following content:

// DO NOT EDIT.
// code generated by goenums 'v0.4.3' at Jun 30 00:49:56.
//
// github.com/zarldev/goenums
//
// using the command:
// goenums -f planets.go

package solarsystem

import (
	"bytes"
	"database/sql/driver"
	"errors"
	"fmt"
	"iter"
	"math"

	"golang.org/x/exp/constraints"
)

// Planet is a type that represents a single enum value.
// It combines the core information about the enum constant and it's defined fields.
type Planet struct {
	planet
	Gravity             float64
	RadiusKm            float64
	MassKg              float64
	OrbitKm             float64
	OrbitDays           float64
	SurfacePressureBars float64
	Moons               int
	Rings               bool
}

// planetsContainer is the container for all enum values.
// It is private and should not be used directly use the public methods on the Planet type.
type planetsContainer struct {
	MERCURY Planet
	VENUS   Planet
	EARTH   Planet
	MARS    Planet
	JUPITER Planet
	SATURN  Planet
	URANUS  Planet
	NEPTUNE Planet
}

// Planets is a main entry point using the Planet type.
// It it a container for all enum values and provides a convenient way to access all enum values and perform
// operations, with convenience methods for common use cases.
var Planets = planetsContainer{
	MERCURY: Planet{
		planet:              mercury,
		Gravity:             0.378,
		RadiusKm:            2439.7,
		MassKg:              3.3e+23,
		OrbitKm:             5.791e+07,
		OrbitDays:           88,
		SurfacePressureBars: 1e-10,
		Moons:               0,
		Rings:               false,
	},
	VENUS: Planet{
		planet:              venus,
		Gravity:             0.907,
		RadiusKm:            6051.8,
		MassKg:              4.87e+24,
		OrbitKm:             1.082e+08,
		OrbitDays:           225,
		SurfacePressureBars: 92,
		Moons:               0,
		Rings:               false,
	},
	EARTH: Planet{
		planet:              earth,
		Gravity:             1,
		RadiusKm:            6378.1,
		MassKg:              5.97e+24,
		OrbitKm:             1.496e+08,
		OrbitDays:           365,
		SurfacePressureBars: 1,
		Moons:               1,
		Rings:               false,
	},
	MARS: Planet{
		planet:              mars,
		Gravity:             0.377,
		RadiusKm:            3389.5,
		MassKg:              6.42e+23,
		OrbitKm:             2.279e+08,
		OrbitDays:           687,
		SurfacePressureBars: 0.01,
		Moons:               2,
		Rings:               false,
	},
	JUPITER: Planet{
		planet:              jupiter,
		Gravity:             2.36,
		RadiusKm:            69911,
		MassKg:              1.9e+27,
		OrbitKm:             7.786e+08,
		OrbitDays:           4333,
		SurfacePressureBars: 20,
		Moons:               4,
		Rings:               true,
	},
	SATURN: Planet{
		planet:              saturn,
		Gravity:             0.916,
		RadiusKm:            58232,
		MassKg:              5.68e+26,
		OrbitKm:             1.4335e+09,
		OrbitDays:           10759,
		SurfacePressureBars: 1,
		Moons:               7,
		Rings:               true,
	},
	URANUS: Planet{
		planet:              uranus,
		Gravity:             0.889,
		RadiusKm:            25362,
		MassKg:              8.68e+25,
		OrbitKm:             2.8725e+09,
		OrbitDays:           30687,
		SurfacePressureBars: 1.3,
		Moons:               13,
		Rings:               true,
	},
	NEPTUNE: Planet{
		planet:              neptune,
		Gravity:             1.12,
		RadiusKm:            24622,
		MassKg:              1.02e+26,
		OrbitKm:             4.4951e+09,
		OrbitDays:           60190,
		SurfacePressureBars: 1.5,
		Moons:               2,
		Rings:               true,
	},
}

// allSlice returns a slice of all enum values.
// This method is useful for iterating over all enum values in a loop.
func (p planetsContainer) allSlice() []Planet {
	return []Planet{
		Planets.MERCURY,
		Planets.VENUS,
		Planets.EARTH,
		Planets.MARS,
		Planets.JUPITER,
		Planets.SATURN,
		Planets.URANUS,
		Planets.NEPTUNE,
	}
}

// All returns an iterator over all enum values.
// This method is useful for iterating over all enum values in a loop.
func (p planetsContainer) All() iter.Seq[Planet] {
	return func(yield func(Planet) bool) {
		for _, v := range p.allSlice() {
			if !yield(v) {
				return
			}
		}
	}
}

var ErrParsePlanet = errors.New("invalid input provided to parse to Planet")

// ParsePlanet parses the input value into an enum value.
// It returns the parsed enum value or an error if the input is invalid.
// It is a convenience function that can be used to parse enum values from
// various input types, such as strings, byte slices, or other enum types.
func ParsePlanet(input any) (Planet, error) {
	switch v := input.(type) {
	case Planet:
		return v, nil
	case string:
		if result := stringToPlanet(v); result != nil {
			return *result, nil
		}
	case fmt.Stringer:
		if result := stringToPlanet(v.String()); result != nil {
			return *result, nil
		}
	case []byte:
		if result := stringToPlanet(string(v)); result != nil {
			return *result, nil
		}
	case int:
		if result := numberToPlanet(v); result != nil {
			return *result, nil
		}
	case int8:
		if result := numberToPlanet(v); result != nil {
			return *result, nil
		}
	case int16:
		if result := numberToPlanet(v); result != nil {
			return *result, nil
		}
	case int32:
		if result := numberToPlanet(v); result != nil {
			return *result, nil
		}
	case int64:
		if result := numberToPlanet(v); result != nil {
			return *result, nil
		}
	case uint:
		if result := numberToPlanet(v); result != nil {
			return *result, nil
		}
	case uint8:
		if result := numberToPlanet(v); result != nil {
			return *result, nil
		}
	case uint16:
		if result := numberToPlanet(v); result != nil {
			return *result, nil
		}
	case uint32:
		if result := numberToPlanet(v); result != nil {
			return *result, nil
		}
	case uint64:
		if result := numberToPlanet(v); result != nil {
			return *result, nil
		}
	case float32:
		if result := numberToPlanet(v); result != nil {
			return *result, nil
		}
	case float64:
		if result := numberToPlanet(v); result != nil {
			return *result, nil
		}
	default:
		return invalidPlanet, fmt.Errorf("invalid type %T", input)
	}
	return invalidPlanet, fmt.Errorf("%w: invalid value %v", ErrParsePlanet, input)
}

// planetsNameMap is a map of enum values to their Planet representation
// It is used to convert string representations of enum values into their Planet representation.
var planetsNameMap = map[string]Planet{
	"Mercury": Planets.MERCURY,
	"Venus":   Planets.VENUS,
	"Earth":   Planets.EARTH,
	"Mars":    Planets.MARS,
	"Jupiter": Planets.JUPITER,
	"Saturn":  Planets.SATURN,
	"Uranus":  Planets.URANUS,
	"Neptune": Planets.NEPTUNE,
}

// stringToPlanet converts a string representation of an enum value into its Planet representation
// It returns a pointer to the Planet representation of the enum value if the string is valid
// Otherwise, it returns nil
func stringToPlanet(s string) *Planet {
	if t, ok := planetsNameMap[s]; ok {
		return &t
	}
	return nil
}

// numberToPlanet converts a numeric value to a Planet
// It returns a pointer to the Planet representation of the enum value if the numeric value is valid
// Otherwise, it returns nil
func numberToPlanet[T constraints.Integer | constraints.Float](num T) *Planet {
	f := float64(num)
	if math.Floor(f) != f {
		return nil
	}
	i := int(f)
	if i <= 0 || i > len(Planets.allSlice()) {
		return nil
	}
	result := Planets.allSlice()[i-1]
	if !result.IsValid() {
		return nil
	}
	return &result
}

// ExhaustivePlanets iterates over all enum values and calls the provided function for each value.
// This function is useful for performing operations on all valid enum values in a loop.
func ExhaustivePlanets(f func(Planet)) {
	for _, p := range Planets.allSlice() {
		f(p)
	}
}

// validPlanets is a map of enum values to their validity
var validPlanets = map[Planet]bool{
	Planets.MERCURY: true,
	Planets.VENUS:   true,
	Planets.EARTH:   true,
	Planets.MARS:    true,
	Planets.JUPITER: true,
	Planets.SATURN:  true,
	Planets.URANUS:  true,
	Planets.NEPTUNE: true,
}

// IsValid checks whether the Planets value is valid.
// A valid value is one that is defined in the original enum and not marked as invalid.
func (p Planet) IsValid() bool {
	return validPlanets[p]
}

// MarshalJSON implements the json.Marshaler interface for Planet.
// It returns the JSON representation of the enum value as a byte slice.
func (p Planet) MarshalJSON() ([]byte, error) {
	return []byte("\"" + p.String() + "\""), nil
}

// UnmarshalJSON implements the json.Unmarshaler interface for Planet.
// It parses the JSON representation of the enum value from the byte slice.
// It returns an error if the input is not a valid JSON representation.
func (p *Planet) UnmarshalJSON(b []byte) error {
	b = bytes.Trim(bytes.Trim(b, "\""), "\"")
	newp, err := ParsePlanet(b)
	if err != nil {
		return err
	}
	*p = newp
	return nil
}

// MarshalText implements the encoding.TextMarshaler interface for Planet.
// It returns the string representation of the enum value as a byte slice
func (p Planet) MarshalText() ([]byte, error) {
	return []byte("\"" + p.String() + "\""), nil
}

// UnmarshalText implements the encoding.TextUnmarshaler interface for Planet.
// It parses the string representation of the enum value from the byte slice.
// It returns an error if the byte slice does not contain a valid enum value.
func (p *Planet) UnmarshalText(b []byte) error {
	newp, err := ParsePlanet(b)
	if err != nil {
		return err
	}
	*p = newp
	return nil
}

// Scan implements the database/sql.Scanner interface for Planet.
// It parses the string representation of the enum value from the database row.
// It returns an error if the row does not contain a valid enum value.
func (p *Planet) Scan(value any) error {
	newp, err := ParsePlanet(value)
	if err != nil {
		return err
	}
	*p = newp
	return nil
}

// Value implements the database/sql/driver.Valuer interface for Planet.
// It returns the string representation of the enum value.
func (p Planet) Value() (driver.Value, error) {
	return p.String(), nil
}

// MarshalBinary implements the encoding.BinaryMarshaler interface for Planet.
// It returns the binary representation of the enum value as a byte slice.
func (p Planet) MarshalBinary() ([]byte, error) {
	return []byte("\"" + p.String() + "\""), nil
}

// UnmarshalBinary implements the encoding.BinaryUnmarshaler interface for Planet.
// It parses the binary representation of the enum value from the byte slice.
// It returns an error if the byte slice does not contain a valid enum value.
func (p *Planet) UnmarshalBinary(b []byte) error {
	newp, err := ParsePlanet(b)
	if err != nil {
		return err
	}
	*p = newp
	return nil
}


// MarshalYAML implements the yaml.Marshaler interface for Planet.
// It returns the string representation of the enum value.
func (p Planet) MarshalYAML() ([]byte, error) {
	return []byte(p.String()), nil
}

// UnmarshalYAML implements the yaml.Unmarshaler interface for Planet.
// It parses the byte slice representation of the enum value and returns an error
// if the YAML byte slice does not contain a valid enum value.
func (p *Planet) UnmarshalYAML(b []byte) error {
	newp, err := ParsePlanet(b)
	if err != nil {
		return err
	}
	*p = newp
	return nil
}

// planetNames is a constant string slice containing all enum values cononical absolute names
const planetNames = "MercuryVenusEarthMarsJupiterSaturnUranusNeptune"

// planetNamesMap is a map of enum values to their canonical absolute
// name positions within the planetNames string slice
var planetNamesMap = map[Planet]string{
	Planets.MERCURY: planetNames[0:7],
	Planets.VENUS:   planetNames[7:12],
	Planets.EARTH:   planetNames[12:17],
	Planets.MARS:    planetNames[17:21],
	Planets.JUPITER: planetNames[21:28],
	Planets.SATURN:  planetNames[28:34],
	Planets.URANUS:  planetNames[34:40],
	Planets.NEPTUNE: planetNames[40:47],
}

// String implements the Stringer interface.
// It returns the canonical absolute name of the enum value.
func (p Planet) String() string {
	if str, ok := planetNamesMap[p]; ok {
		return str
	}
	return fmt.Sprintf("planet(%d)", p.planet)
}

// Compile-time check that all enum values are valid.
// This function is used to ensure that all enum values are defined and valid.
// It is called by the compiler to verify that the enum values are valid.
func _() {
	// An "invalid array index" compiler error signifies that the constant values have changed.
	// Re-run the goenums command to generate them again.
	// Does not identify newly added constant values unless order changes
	var x [9]struct{}
	_ = x[mercury-1]
	_ = x[venus-2]
	_ = x[earth-3]
	_ = x[mars-4]
	_ = x[jupiter-5]
	_ = x[saturn-6]
	_ = x[uranus-7]
	_ = x[neptune-8]
}

For more examples, see those used for testing in the testdata directory.

Mentions

go-recipes

License

MIT License - See LICENSE for full details.

Documentation

Overview

The goenums tool addresses Go's lack of native enum support by generating type-safe wrappers around iota-based constant declarations.

It analyzes Go source files to identify iota-based constant groups and produces comprehensive enum implementations with rich functionality including string conversion, JSON/database integration, validation, and iteration support.

Key Features

  • Type-safe enum wrapper types with custom fields
  • Comprehensive string conversion and parsing (with optional case-insensitive mode)
  • JSON, Text, Binary, and YAML marshaling/unmarshaling
  • SQL database integration via Scanner/Valuer interfaces
  • Validation methods for checking valid enum values
  • Go 1.23+ iterator support with automatic legacy fallback
  • Exhaustive processing to ensure all values are handled
  • Numeric parsing from underlying integer values
  • Alias support for alternative enum names

Basic Usage

Define an enum in your Go source file:

type Status int
const (
    Active Status = iota
    Inactive
    Pending
)

Generate the enum implementation:

goenums status.go

Use the generated enum:

status := Statuses.ACTIVE
fmt.Println(status.String())        // "Active"
parsed, _ := ParseStatus("Pending") // Statuses.PENDING
fmt.Println(status.IsValid())       // true

Advanced Features

## Custom Fields

Add metadata to enum values using field syntax:

type HTTPStatus int // code[int], message[string]
const (
    OK HTTPStatus = iota         // 200, "Success"
    NotFound                     // 404, "Not Found"
    InternalError                // 500, "Internal Server Error"
)

Access custom fields in generated code:

status := HTTPStatuses.OK
fmt.Println(status.Code())    // 200
fmt.Println(status.Message()) // "Success"

## JSON Integration

type Task struct {
    Name     string     `json:"name"`
    Priority Priority   `json:"priority"`
}

task := Task{Name: "Deploy", Priority: Priorities.HIGH}
json.Marshal(task) // {"name":"Deploy","priority":"High"}

## Database Integration

// Enums implement database/sql interfaces automatically
var status Status
err := db.QueryRow("SELECT status FROM tasks WHERE id = ?", id).Scan(&status)

_, err = db.Exec("INSERT INTO tasks (status) VALUES (?)", Statuses.ACTIVE)

## Iteration (Go 1.23+)

for status := range Statuses.All() {
    fmt.Println("Status:", status.String())
}

Command Line Options

goenums [options] file.go

-f, -failfast      Fail on invalid enum values during parsing
-l, -legacy        Generate code without Go 1.23+ iterator support
-i, -insensitive   Enable case-insensitive string parsing
-c, -constraints   Generate constraints locally instead of importing
-v, -version       Show version information
-h, -help          Show help information
-vv, -verbose      Enable verbose output
-o, -output        Specify output format (default: go)

Design Philosophy

The tool follows a modular, interface-based architecture that separates content sourcing, parsing, and code generation. This design enables:

  • Support for different input formats (currently Go, extensible to others)
  • Multiple output targets (currently Go, extensible to other languages)
  • Clean separation of concerns between components
  • Easy testing and maintenance of individual components
  • Future extensibility without breaking existing functionality

The generated code prioritizes type safety, performance, and integration with standard Go interfaces and conventions.

Directories

Path Synopsis
Package enum defines the core interfaces and data structures for enum representation.
Package enum defines the core interfaces and data structures for enum representation.
Package examples contains example enum definitions for testing and documentation purposes.
Package examples contains example enum definitions for testing and documentation purposes.
The file package provides utilities for file I/O operations with specific handling for Go source files, including automatic formatting.
The file package provides utilities for file I/O operations with specific handling for Go source files, including automatic formatting.
The generator package coordinates the workflow of enum parsing and generation.
The generator package coordinates the workflow of enum parsing and generation.
config
Package config defines configuration options for the enum generation process.
Package config defines configuration options for the enum generation process.
gofile
The gofile package provides Go-specific parsing and generation capabilities.
The gofile package provides Go-specific parsing and generation capabilities.
internal
version
Package version provides version information for the goenums tool.
Package version provides version information for the goenums tool.
Package logging provides customized structured logging functionality.
Package logging provides customized structured logging functionality.
The source package provides implementations of the enum.Source interface.
The source package provides implementations of the enum.Source interface.
Package strings provides specialized string handling utilities for enum generation.
Package strings provides specialized string handling utilities for enum generation.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL