Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
176 changes: 176 additions & 0 deletions CLAUDE.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,176 @@
# CLAUDE.md

This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.

## Project Overview

`enum` is a Go code generator that creates type-safe, marshalable enum implementations from simple type definitions. It generates idiomatic Go code with zero runtime dependencies and supports JSON, SQL, MongoDB BSON, and YAML marshaling through optional flags.

## Commands

### Build and Test
```bash
# Run all tests (excludes examples)
go test ./...

# Run tests with race detection and coverage
go test -race -cover ./...

# Run a specific test
go test -run TestRuntimeIntegration ./internal/generator

# Run integration tests (includes MongoDB container tests)
go test ./internal/generator -v -run TestRuntimeIntegration

# Build the enum generator
go build

# Install globally
go install github.com/go-pkgz/enum@latest
```

### Linting and Formatting
```bash
# Run linter (golangci-lint v2.0.2)
golangci-lint run

# Format code
gofmt -s -w .
goimports -w .
```

### Generate Enums
```bash
# Generate using go:generate directives
go generate ./...

# Direct invocation examples
go run github.com/go-pkgz/enum@latest -type status -lower
go run github.com/go-pkgz/enum@latest -type status -lower -sql -bson -yaml
```

## Architecture

### Core Components

1. **main.go** - CLI entry point that parses flags and invokes the generator
2. **internal/generator/generator.go** - Core generator logic:
- Parses Go AST to find enum constants
- Evaluates constant values including iota and binary expressions
- Generates code from template with conditional blocks for features
3. **internal/generator/enum.go.tmpl** - Go template for generated code with conditional sections for SQL/BSON/YAML

### Key Design Decisions

1. **Type name must be lowercase (private)** - Enforced to prevent confusion with public types
2. **Constants must be prefixed with type name** - Ensures clear namespacing (e.g., `statusActive` for type `status`)
3. **Generated public types are capitalized** - Follows Go conventions (private `status` → public `Status`)
4. **Zero runtime dependencies** - Generated code uses only stdlib unless optional features are enabled
5. **Conditional feature generation** - SQL/BSON/YAML code only generated when flags are set to avoid forcing dependencies

### Integration Support

- **JSON**: Via `encoding.TextMarshaler`/`TextUnmarshaler` (always generated)
- **SQL** (`-sql` flag): Implements `database/sql/driver.Valuer` and `sql.Scanner`
- Smart NULL handling: uses zero value if available, errors otherwise
- **BSON** (`-bson` flag): Implements `MarshalBSONValue`/`UnmarshalBSONValue` for MongoDB
- Stores as string values, not documents
- **YAML** (`-yaml` flag): Implements `yaml.Marshaler`/`yaml.Unmarshaler` for gopkg.in/yaml.v3

### Testing Strategy

1. **Unit tests** (`generator_test.go`): Test parsing, generation, edge cases
2. **Integration tests** (`integration_test.go`):
- `TestRuntimeIntegration`: Full pipeline test that builds binary, generates code, runs tests with real databases
- Uses testcontainers for MongoDB integration testing
- SQLite for SQL testing (in-memory)
3. **Test data** in `testdata/integration/`:
- `enum_test.go`: Real database tests run by runtime integration
- `status.go`, `priority.go`: Sample enums for testing

### Parsing and Generation Flow

1. Parse Go source files to find type definition
2. Extract constants prefixed with type name
3. Evaluate constant values:
- Handle iota increments
- Evaluate binary expressions (e.g., `iota + 1`, `1 << iota`)
- Support explicit values
4. Generate code with:
- String() method
- Parse/Must functions
- Marshal/Unmarshal methods based on flags
- Iterator for Go 1.23+ range-over-func
- Optional GetByID function (requires unique values)

## Important Constraints

1. **Enum type must be lowercase** - Generator validates and rejects uppercase type names
2. **Constants must start with type name** - e.g., for type `status`, constants must be `statusXxx`
3. **Unique IDs required for -getter flag** - Generator fails if duplicate values exist when getter is requested
4. **Declaration order preserved** - Enums maintain source order, not alphabetical
5. **Type fidelity** - Generated code preserves underlying type (uint8, int32, etc.)

## CI/CD

GitHub Actions workflow (`.github/workflows/ci.yml`):
- Runs on all pushes and PRs
- Tests with Go 1.24
- Runs tests with race detection and coverage
- Runs golangci-lint
- Submits coverage to Coveralls
- Tests examples separately

## Integration Testing Architecture

### Test Structure
The integration tests use a unique two-stage approach:

1. **`TestRuntimeIntegration`** in `integration_test.go`:
- Builds the enum binary from source
- Creates a temporary package with enum definitions
- Runs the built binary to generate enum code
- Creates a temporary `go.mod` with test dependencies
- Copies test file from `testdata/integration/enum_test.go`
- Runs the generated tests in isolation

2. **Actual database tests** in `testdata/integration/enum_test.go`:
- `TestGeneratedEnumWithMongoDB`: Uses `github.com/go-pkgz/testutils` to spin up MongoDB 7 container
- `TestGeneratedEnumWithSQL`: Uses in-memory SQLite for SQL testing
- Tests real marshal/unmarshal operations, not just code generation

### Key Integration Test Patterns

1. **Test files in testdata are NOT compiled by Go** - They're copied and run in temp directory
2. **MongoDB container via testutils**:
```go
mongoContainer := containers.NewMongoTestContainer(ctx, t, 7)
defer mongoContainer.Close(ctx)
coll := mongoContainer.Collection("test_db")
```
3. **Verifies storage format** - Confirms enums stored as strings in MongoDB, not empty documents
4. **Full round-trip testing** - Writes enum to database, reads back, verifies correct unmarshaling

### Running Integration Tests
```bash
# Run full integration test (builds binary, generates code, tests with real databases)
go test ./internal/generator -v -run TestRuntimeIntegration

# Skip integration tests in short mode
go test -short ./...

# Clean test cache before running to ensure fresh MongoDB container
go clean -testcache && go test ./internal/generator -v -run TestRuntimeIntegration
```

### Test Dependencies
Integration tests require:
- Docker for MongoDB containers (via testcontainers)
- Network access for go mod tidy in temp package
- Write access to temp directory for generated code

### Important Testing Details
- `TestMongoDBIntegration` in main `integration_test.go` only verifies code generation, not actual MongoDB
- Real MongoDB testing happens via `TestRuntimeIntegration` → `TestGeneratedEnumWithMongoDB`
- Tests verify both success and error paths (NULL handling, invalid values)
- Uses `require` for critical assertions, `assert` for non-critical ones
50 changes: 44 additions & 6 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,12 +1,13 @@
# enum [![Build Status](https://github.com/go-pkgz/enum/workflows/build/badge.svg)](https://github.com/go-pkgz/enum/actions) [![Coverage Status](https://coveralls.io/repos/github/go-pkgz/enum/badge.svg?branch=master)](https://coveralls.io/github/go-pkgz/enum?branch=master) [![godoc](https://godoc.org/github.com/go-pkgz/enum?status.svg)](https://godoc.org/github.com/go-pkgz/enum)


`enum` is a Go package that provides a code generator for type-safe, json/bson/text-marshalable enumerations. It creates idiomatic Go code from simple type definitions, supporting both case-sensitive and case-insensitive string representations.
`enum` is a Go package that provides a code generator for type-safe, json/text-marshalable enumerations. Optional flags add SQL, BSON (MongoDB), and YAML support. It creates idiomatic Go code from simple type definitions, supporting both case-sensitive and case-insensitive string representations.

## Features

- Type-safe enum implementations
- JSON, BSON, SQL and text marshaling/unmarshaling support
- Text marshaling/unmarshaling (JSON works via TextMarshaler)
- Optional SQL, BSON (MongoDB), and YAML support via flags
- Case-sensitive or case-insensitive string representations
- Panic-free parsing with error handling
- Must-style parsing variants for convenience
Expand Down Expand Up @@ -97,12 +98,17 @@ const (
go generate ./...
```

By default the generated type supports `encoding.TextMarshaler`/`Unmarshaler` (used by `encoding/json`). To include other integrations, enable flags as needed (see below).

### Generator Options

- `-type` (required): the name of the type to generate enum for (must be lowercase/private)
- `-path`: output directory path (default: same as source)
- `-lower`: use lowercase for string representations when marshaling/unmarshaling (affects only the output strings, not the naming pattern)
- `-lower`: use lowercase for string representations when marshaling/unmarshaling
- `-getter`: enables the generation of an additional function, `Get{{Type}}ByID`, which attempts to find the corresponding enum element by its underlying integer ID. The `-getter` flag requires enum elements to have unique IDs to prevent undefined behavior.
- `-sql` (default: off): add SQL support via `database/sql/driver.Valuer` and `sql.Scanner`
- `-bson` (default: off): add MongoDB BSON support via `MarshalBSONValue`/`UnmarshalBSONValue`
- `-yaml` (default: off): add YAML support via `gopkg.in/yaml.v3` `Marshaler`/`Unmarshaler`
- `-version`: print version information
- `-help`: show usage information

Expand All @@ -112,7 +118,7 @@ The generator creates a new type with the following features:

- String representation (implements `fmt.Stringer`)
- Text marshaling (implements `encoding.TextMarshaler` and `encoding.TextUnmarshaler`)
- SQL support (implements `database/sql/driver.Valuer` and `sql.Scanner`)
- SQL support when `-sql` is set (implements `database/sql/driver.Valuer` and `sql.Scanner`)
- Parse function with error handling (`ParseStatus`) - uses efficient O(1) map lookup
- Must-style parse function that panics on error (`MustStatus`)
- All possible values as package variable (`StatusValues`) - preserves declaration order
Expand All @@ -123,6 +129,38 @@ The generator creates a new type with the following features:

Additionally, if the `-getter` flag is set, a getter function (`GetStatusByID`) will be generated. This function allows retrieving an enum element using its raw integer ID.

### JSON, BSON, YAML

- JSON: works out of the box through `encoding.TextMarshaler`/`Unmarshaler`.
- BSON (MongoDB): enable `-bson` to generate `MarshalBSONValue`/`UnmarshalBSONValue`; values are stored as strings.
- YAML: enable `-yaml` to generate `MarshalYAML`/`UnmarshalYAML`; values are encoded as strings.

Example (MongoDB using `-bson`):

```go
//go:generate go run github.com/go-pkgz/enum@latest -type status -bson -lower

type status uint8
const (
statusUnknown status = iota
statusActive
statusInactive
)

// Using mongo-go-driver
type User struct {
ID primitive.ObjectID `bson:"_id,omitempty"`
Status Status `bson:"status"`
}

// insert and read
u := User{Status: StatusActive}
_, _ = coll.InsertOne(ctx, u) // stores { status: "active" }

var out User
_ = coll.FindOne(ctx, bson.M{"status": "active"}).Decode(&out) // decodes via UnmarshalBSONValue
```

### Case Sensitivity

By default, the generator creates case-sensitive string representations. Use `-lower` flag for lowercase output:
Expand Down Expand Up @@ -156,7 +194,7 @@ if err != nil {
status := MustStatus("active") // panics if invalid
```

### SQL Database Support
### SQL Database Support (with `-sql`)

The generated enums implement `database/sql/driver.Valuer` and `sql.Scanner` interfaces for seamless database integration:

Expand Down Expand Up @@ -186,4 +224,4 @@ Contributions are welcome! Please feel free to submit a Pull Request.

## License

MIT License
MIT License
28 changes: 28 additions & 0 deletions _examples/status/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
Example: status enum

This example shows how to generate enums with optional integrations.

Current files were generated previously; to regenerate with the new flags, run from this folder:

```
# JSON only (default TextMarshaler/Unmarshaler, no extra deps)
go run ../../main.go -type status -lower

# Add SQL support
go run ../../main.go -type status -lower -sql

# Add MongoDB BSON support
go run ../../main.go -type status -lower -bson

# Add YAML support
go run ../../main.go -type status -lower -yaml

# Combine as needed, e.g. BSON + SQL
go run ../../main.go -type status -lower -bson -sql
```

Notes
- `-bson` uses mongo-go-driver BSON interfaces; values are stored as strings.
- `-sql` implements `driver.Valuer` and `sql.Scanner`.
- `-yaml` implements `yaml.Marshaler`/`yaml.Unmarshaler` (gopkg.in/yaml.v3).

Loading
Loading