enum

package module
v0.0.0-...-60c47ad Latest Latest
Warning

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

Go to latest
Published: Jun 21, 2025 License: MIT Imports: 6 Imported by: 0

README

Zero-maintenance enum solution for Go

Due to poor enum support in Go, a pattern has emerged where developers:

  • create types with primitive underlying types (e.g type Status string, type LogLevel int)
  • define set of values, e.g const StatusDraft = Status("open")
  • implement IsValid() bool / Validate() error for validation (usually against user input).

The annoying part is maintaining the list of values while keeping validation logic in sync.
Small thing, but the lack of single source of truth makes us feel dumb, detached, and sad.

Features

  • Inline definitions: register enum values as you declare them
  • Zero-maintenance: automatic IsValid() bool and Validate() error functions without code generation
  • No interference: no wrappers or no new type - definitions keep their original type and value
  • Scoped: definitions are scoped to their type, e.g "active" can be valid value for type UserStatus string, but not necessarily for type OrderStatus string
  • User friendly error message: validation error message is human-readable and helpful
  • Lightweight: no dependencies, auditable (less than 200 lines of code)

Installation

go get github.com/0xcafe-io/enum

Usage

package main

import (
	"fmt"

	"github.com/0xcafe-io/enum"
)

type Status string

var (
	StatusDraft  = enum.Def[Status]("draft")
	StatusOpen   = enum.Def[Status]("open")
	StatusMerged = enum.Def[Status]("merged")
	StatusClosed = enum.Def[Status]("closed")
)

func main() {
	userInput := "postponed"
	status := Status(userInput)

	if !enum.IsValid(status) {
		fmt.Println("bad status")
	}

	if err := enum.Validate(status); err != nil {
		fmt.Println(err)
	}

	status = "merged"
	if enum.IsValid(status) {
		fmt.Println("good status")
	}

	if status == StatusMerged {
		fmt.Println("nice job")
	}

	statuses := enum.ValuesOf[Status]()
	fmt.Println(statuses)

	// Output:
	// bad status
	// "postponed" is not a valid choice, allowed values are: "draft", "open", "merged", "closed"
	// good status
	// nice job
	// [draft open merged closed]
}

Integer types are also supported (int, int8/byte, int16, int32/rune, int64, and their unsigned siblings):

package main

import (
	"fmt"

	"github.com/0xcafe-io/enum"
)

type Access int

var (
	AccessRead    = enum.Def[Access](1)
	AccessComment = enum.Def[Access](2)
	AccessWrite   = enum.Def[Access](4)
)

func main() {
	var access Access
	if !enum.IsValid(access) {
		fmt.Println("access denied")
	}

	access = 2
	if enum.IsValid(access) {
		fmt.Println("access granted")
	}

	if access == AccessComment {
		fmt.Println("can comment")
	}

	if err := enum.Validate[Access](99); err != nil {
		fmt.Println(err)
	}

	// Output:
	// access denied
	// access granted
	// can comment
	// 99 is not a valid choice, allowed values are: 1, 2, 4
}

Limitations

  • var instead of const for definitions due to function call

Documentation

Overview

Package enum provides a way to define and work with enums in Go. The main benefit is to have IsValid function with zero-maintenance.

Example (Empty)
package main

import (
	"fmt"

	"github.com/0xcafe-io/enum"
)

func main() {
	type Nothing int
	if err := enum.Validate[Nothing](1); err != nil {
		fmt.Println(err)
	}
}
Output:

Nothing doesn't have any definition
Example (Int)
package main

import (
	"fmt"

	"github.com/0xcafe-io/enum"
)

type Access int

var AccessComment = enum.Def[Access](2)

func main() {
	var access Access

	if !enum.IsValid(access) {
		fmt.Println("access denied")
	}

	access = 2
	if enum.IsValid(access) {
		fmt.Println("access granted")
	}

	if access == AccessComment {
		fmt.Println("can comment")
	}

	if err := enum.Validate[Access](99); err != nil {
		fmt.Println(err)
	}

}
Output:

access denied
access granted
can comment
99 is not a valid choice, allowed values are: 1, 2, 4
Example (Scope)
package main

import (
	"fmt"

	"github.com/0xcafe-io/enum"
)

func main() {
	type OrderStatus string
	type UserStatus string
	var (
		OrderStatusPending    = enum.Def[OrderStatus]("pending")
		OrderStatusInProgress = enum.Def[OrderStatus]("in_progress")

		UserStatusActive = enum.Def[UserStatus]("pending")
		UserStatusBanned = enum.Def[UserStatus]("banned")
	)
	_, _, _, _ = OrderStatusPending, OrderStatusInProgress, UserStatusActive, UserStatusBanned

	if enum.IsValid[OrderStatus]("pending") {
		fmt.Println("pending is a valid order status")
	}
	if enum.IsValid[UserStatus]("pending") {
		fmt.Println("pending is a valid user status")
	}
	if !enum.IsValid[OrderStatus]("banned") {
		fmt.Println("banned is not a valid order status")
	}
	if !enum.IsValid[UserStatus]("in_progress") {
		fmt.Println("in_progress is not a valid user status")
	}
}
Output:

pending is a valid order status
pending is a valid user status
banned is not a valid order status
in_progress is not a valid user status
Example (String)
package main

import (
	"fmt"

	"github.com/0xcafe-io/enum"
)

type Status string

var StatusMerged = enum.Def[Status]("merged")

func main() {
	userInput := "postponed"
	status := Status(userInput)

	if !enum.IsValid(status) {
		fmt.Println("bad status")
	}

	if err := enum.Validate(status); err != nil {
		fmt.Println(err)
	}

	status = "merged"
	if enum.IsValid(status) {
		fmt.Println("good status")
	}

	if status == StatusMerged {
		fmt.Println("nice job")
	}

	statuses := enum.ValuesOf[Status]()
	fmt.Println(statuses)

}
Output:

bad status
"postponed" is not a valid choice, allowed values are: "draft", "open", "merged", "closed"
good status
nice job
[draft open merged closed]

Index

Examples

Constants

This section is empty.

Variables

This section is empty.

Functions

func Clear

func Clear[T enumType]()

Clear removes all definitions for enum T.

func Def

func Def[T enumType](v T) T

Def defines v as a valid value of enum T and returns it. Value is returned as-is, without any wrapping or conversion. Duplicate definitions are ignored. Usage:

type Status string
var (
  StatusDraft  = enum.Def(Status("draft")) // type of StatusDraft is Status
  StatusOpen   = enum.Def[Status]("open") // same thing, alternative syntax
)

func IsValid

func IsValid[T enumType](v T) bool

IsValid reports whether v is defined for enum T.

func Validate

func Validate[T enumType](v T) error

Validate checks whether v is defined for enum T. If not, returns an error, otherwise returns nil.

func ValuesOf

func ValuesOf[T enumType]() []T

ValuesOf returns defined values of enum T. Values are returned in the order they were mentioned (see https://go.dev/ref/spec#Package_initialization). It is safe to modify the returned slice.

Types

This section is empty.

Jump to

Keyboard shortcuts

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