Skip to content

Commit 6e75014

Browse files
committed
feat(generator): add BSON, SQL, and YAML support with conditional generation
Implement conditional code generation for database and serialization support: - Add -sql flag for database/sql driver.Valuer and sql.Scanner interfaces - Add -bson flag for MongoDB BSON marshaling/unmarshaling support - Add -yaml flag for gopkg.in/yaml.v3 support - All features are opt-in via flags to avoid forcing dependencies Key improvements: - BSON values stored as strings in MongoDB (fixes empty document issue) - Smart SQL NULL handling using zero values when available - Comprehensive integration tests with real MongoDB and SQLite - Runtime integration test that builds binary and verifies generated code - Test coverage for all marshal/unmarshal operations The generator now supports multiple database and serialization formats while maintaining zero runtime dependencies when features aren't used.
1 parent 1834756 commit 6e75014

File tree

17 files changed

+1947
-54
lines changed

17 files changed

+1947
-54
lines changed

CLAUDE.md

Lines changed: 176 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,176 @@
1+
# CLAUDE.md
2+
3+
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
4+
5+
## Project Overview
6+
7+
`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.
8+
9+
## Commands
10+
11+
### Build and Test
12+
```bash
13+
# Run all tests (excludes examples)
14+
go test ./...
15+
16+
# Run tests with race detection and coverage
17+
go test -race -cover ./...
18+
19+
# Run a specific test
20+
go test -run TestRuntimeIntegration ./internal/generator
21+
22+
# Run integration tests (includes MongoDB container tests)
23+
go test ./internal/generator -v -run TestRuntimeIntegration
24+
25+
# Build the enum generator
26+
go build
27+
28+
# Install globally
29+
go install github.com/go-pkgz/enum@latest
30+
```
31+
32+
### Linting and Formatting
33+
```bash
34+
# Run linter (golangci-lint v2.0.2)
35+
golangci-lint run
36+
37+
# Format code
38+
gofmt -s -w .
39+
goimports -w .
40+
```
41+
42+
### Generate Enums
43+
```bash
44+
# Generate using go:generate directives
45+
go generate ./...
46+
47+
# Direct invocation examples
48+
go run github.com/go-pkgz/enum@latest -type status -lower
49+
go run github.com/go-pkgz/enum@latest -type status -lower -sql -bson -yaml
50+
```
51+
52+
## Architecture
53+
54+
### Core Components
55+
56+
1. **main.go** - CLI entry point that parses flags and invokes the generator
57+
2. **internal/generator/generator.go** - Core generator logic:
58+
- Parses Go AST to find enum constants
59+
- Evaluates constant values including iota and binary expressions
60+
- Generates code from template with conditional blocks for features
61+
3. **internal/generator/enum.go.tmpl** - Go template for generated code with conditional sections for SQL/BSON/YAML
62+
63+
### Key Design Decisions
64+
65+
1. **Type name must be lowercase (private)** - Enforced to prevent confusion with public types
66+
2. **Constants must be prefixed with type name** - Ensures clear namespacing (e.g., `statusActive` for type `status`)
67+
3. **Generated public types are capitalized** - Follows Go conventions (private `status` → public `Status`)
68+
4. **Zero runtime dependencies** - Generated code uses only stdlib unless optional features are enabled
69+
5. **Conditional feature generation** - SQL/BSON/YAML code only generated when flags are set to avoid forcing dependencies
70+
71+
### Integration Support
72+
73+
- **JSON**: Via `encoding.TextMarshaler`/`TextUnmarshaler` (always generated)
74+
- **SQL** (`-sql` flag): Implements `database/sql/driver.Valuer` and `sql.Scanner`
75+
- Smart NULL handling: uses zero value if available, errors otherwise
76+
- **BSON** (`-bson` flag): Implements `MarshalBSONValue`/`UnmarshalBSONValue` for MongoDB
77+
- Stores as string values, not documents
78+
- **YAML** (`-yaml` flag): Implements `yaml.Marshaler`/`yaml.Unmarshaler` for gopkg.in/yaml.v3
79+
80+
### Testing Strategy
81+
82+
1. **Unit tests** (`generator_test.go`): Test parsing, generation, edge cases
83+
2. **Integration tests** (`integration_test.go`):
84+
- `TestRuntimeIntegration`: Full pipeline test that builds binary, generates code, runs tests with real databases
85+
- Uses testcontainers for MongoDB integration testing
86+
- SQLite for SQL testing (in-memory)
87+
3. **Test data** in `testdata/integration/`:
88+
- `enum_test.go`: Real database tests run by runtime integration
89+
- `status.go`, `priority.go`: Sample enums for testing
90+
91+
### Parsing and Generation Flow
92+
93+
1. Parse Go source files to find type definition
94+
2. Extract constants prefixed with type name
95+
3. Evaluate constant values:
96+
- Handle iota increments
97+
- Evaluate binary expressions (e.g., `iota + 1`, `1 << iota`)
98+
- Support explicit values
99+
4. Generate code with:
100+
- String() method
101+
- Parse/Must functions
102+
- Marshal/Unmarshal methods based on flags
103+
- Iterator for Go 1.23+ range-over-func
104+
- Optional GetByID function (requires unique values)
105+
106+
## Important Constraints
107+
108+
1. **Enum type must be lowercase** - Generator validates and rejects uppercase type names
109+
2. **Constants must start with type name** - e.g., for type `status`, constants must be `statusXxx`
110+
3. **Unique IDs required for -getter flag** - Generator fails if duplicate values exist when getter is requested
111+
4. **Declaration order preserved** - Enums maintain source order, not alphabetical
112+
5. **Type fidelity** - Generated code preserves underlying type (uint8, int32, etc.)
113+
114+
## CI/CD
115+
116+
GitHub Actions workflow (`.github/workflows/ci.yml`):
117+
- Runs on all pushes and PRs
118+
- Tests with Go 1.24
119+
- Runs tests with race detection and coverage
120+
- Runs golangci-lint
121+
- Submits coverage to Coveralls
122+
- Tests examples separately
123+
124+
## Integration Testing Architecture
125+
126+
### Test Structure
127+
The integration tests use a unique two-stage approach:
128+
129+
1. **`TestRuntimeIntegration`** in `integration_test.go`:
130+
- Builds the enum binary from source
131+
- Creates a temporary package with enum definitions
132+
- Runs the built binary to generate enum code
133+
- Creates a temporary `go.mod` with test dependencies
134+
- Copies test file from `testdata/integration/enum_test.go`
135+
- Runs the generated tests in isolation
136+
137+
2. **Actual database tests** in `testdata/integration/enum_test.go`:
138+
- `TestGeneratedEnumWithMongoDB`: Uses `github.com/go-pkgz/testutils` to spin up MongoDB 7 container
139+
- `TestGeneratedEnumWithSQL`: Uses in-memory SQLite for SQL testing
140+
- Tests real marshal/unmarshal operations, not just code generation
141+
142+
### Key Integration Test Patterns
143+
144+
1. **Test files in testdata are NOT compiled by Go** - They're copied and run in temp directory
145+
2. **MongoDB container via testutils**:
146+
```go
147+
mongoContainer := containers.NewMongoTestContainer(ctx, t, 7)
148+
defer mongoContainer.Close(ctx)
149+
coll := mongoContainer.Collection("test_db")
150+
```
151+
3. **Verifies storage format** - Confirms enums stored as strings in MongoDB, not empty documents
152+
4. **Full round-trip testing** - Writes enum to database, reads back, verifies correct unmarshaling
153+
154+
### Running Integration Tests
155+
```bash
156+
# Run full integration test (builds binary, generates code, tests with real databases)
157+
go test ./internal/generator -v -run TestRuntimeIntegration
158+
159+
# Skip integration tests in short mode
160+
go test -short ./...
161+
162+
# Clean test cache before running to ensure fresh MongoDB container
163+
go clean -testcache && go test ./internal/generator -v -run TestRuntimeIntegration
164+
```
165+
166+
### Test Dependencies
167+
Integration tests require:
168+
- Docker for MongoDB containers (via testcontainers)
169+
- Network access for go mod tidy in temp package
170+
- Write access to temp directory for generated code
171+
172+
### Important Testing Details
173+
- `TestMongoDBIntegration` in main `integration_test.go` only verifies code generation, not actual MongoDB
174+
- Real MongoDB testing happens via `TestRuntimeIntegration``TestGeneratedEnumWithMongoDB`
175+
- Tests verify both success and error paths (NULL handling, invalid values)
176+
- Uses `require` for critical assertions, `assert` for non-critical ones

README.md

Lines changed: 44 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,13 @@
11
# 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)
22

33

4-
`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.
4+
`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.
55

66
## Features
77

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

101+
By default the generated type supports `encoding.TextMarshaler`/`Unmarshaler` (used by `encoding/json`). To include other integrations, enable flags as needed (see below).
102+
100103
### Generator Options
101104

102105
- `-type` (required): the name of the type to generate enum for (must be lowercase/private)
103106
- `-path`: output directory path (default: same as source)
104-
- `-lower`: use lowercase for string representations when marshaling/unmarshaling (affects only the output strings, not the naming pattern)
107+
- `-lower`: use lowercase for string representations when marshaling/unmarshaling
105108
- `-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.
109+
- `-sql` (default: off): add SQL support via `database/sql/driver.Valuer` and `sql.Scanner`
110+
- `-bson` (default: off): add MongoDB BSON support via `MarshalBSONValue`/`UnmarshalBSONValue`
111+
- `-yaml` (default: off): add YAML support via `gopkg.in/yaml.v3` `Marshaler`/`Unmarshaler`
106112
- `-version`: print version information
107113
- `-help`: show usage information
108114

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

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

124130
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.
125131

132+
### JSON, BSON, YAML
133+
134+
- JSON: works out of the box through `encoding.TextMarshaler`/`Unmarshaler`.
135+
- BSON (MongoDB): enable `-bson` to generate `MarshalBSONValue`/`UnmarshalBSONValue`; values are stored as strings.
136+
- YAML: enable `-yaml` to generate `MarshalYAML`/`UnmarshalYAML`; values are encoded as strings.
137+
138+
Example (MongoDB using `-bson`):
139+
140+
```go
141+
//go:generate go run github.com/go-pkgz/enum@latest -type status -bson -lower
142+
143+
type status uint8
144+
const (
145+
statusUnknown status = iota
146+
statusActive
147+
statusInactive
148+
)
149+
150+
// Using mongo-go-driver
151+
type User struct {
152+
ID primitive.ObjectID `bson:"_id,omitempty"`
153+
Status Status `bson:"status"`
154+
}
155+
156+
// insert and read
157+
u := User{Status: StatusActive}
158+
_, _ = coll.InsertOne(ctx, u) // stores { status: "active" }
159+
160+
var out User
161+
_ = coll.FindOne(ctx, bson.M{"status": "active"}).Decode(&out) // decodes via UnmarshalBSONValue
162+
```
163+
126164
### Case Sensitivity
127165

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

159-
### SQL Database Support
197+
### SQL Database Support (with `-sql`)
160198

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

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

187225
## License
188226

189-
MIT License
227+
MIT License

_examples/status/README.md

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
Example: status enum
2+
3+
This example shows how to generate enums with optional integrations.
4+
5+
Current files were generated previously; to regenerate with the new flags, run from this folder:
6+
7+
```
8+
# JSON only (default TextMarshaler/Unmarshaler, no extra deps)
9+
go run ../../main.go -type status -lower
10+
11+
# Add SQL support
12+
go run ../../main.go -type status -lower -sql
13+
14+
# Add MongoDB BSON support
15+
go run ../../main.go -type status -lower -bson
16+
17+
# Add YAML support
18+
go run ../../main.go -type status -lower -yaml
19+
20+
# Combine as needed, e.g. BSON + SQL
21+
go run ../../main.go -type status -lower -bson -sql
22+
```
23+
24+
Notes
25+
- `-bson` uses mongo-go-driver BSON interfaces; values are stored as strings.
26+
- `-sql` implements `driver.Valuer` and `sql.Scanner`.
27+
- `-yaml` implements `yaml.Marshaler`/`yaml.Unmarshaler` (gopkg.in/yaml.v3).
28+

0 commit comments

Comments
 (0)