diff --git a/CLAUDE.md b/CLAUDE.md new file mode 100644 index 0000000..118291b --- /dev/null +++ b/CLAUDE.md @@ -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 \ No newline at end of file diff --git a/README.md b/README.md index ccf55b7..e3bb58d 100644 --- a/README.md +++ b/README.md @@ -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 @@ -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 @@ -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 @@ -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: @@ -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: @@ -186,4 +224,4 @@ Contributions are welcome! Please feel free to submit a Pull Request. ## License -MIT License \ No newline at end of file +MIT License diff --git a/_examples/status/README.md b/_examples/status/README.md new file mode 100644 index 0000000..b7d250d --- /dev/null +++ b/_examples/status/README.md @@ -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). + diff --git a/_examples/status/job_status_enum.go b/_examples/status/job_status_enum.go index e4b1b4a..9669dee 100644 --- a/_examples/status/job_status_enum.go +++ b/_examples/status/job_status_enum.go @@ -2,19 +2,21 @@ package status import ( - "fmt" - "database/sql/driver" + "fmt" ) // JobStatus is the exported type for the enum type JobStatus struct { name string - value int + value uint8 } func (e JobStatus) String() string { return e.name } +// Index returns the underlying integer value +func (e JobStatus) Index() uint8 { return e.value } + // MarshalText implements encoding.TextMarshaler func (e JobStatus) MarshalText() ([]byte, error) { return []byte(e.name), nil @@ -35,8 +37,15 @@ func (e JobStatus) Value() (driver.Value, error) { // Scan implements the sql.Scanner interface func (e *JobStatus) Scan(value interface{}) error { if value == nil { - *e = JobStatusValues()[0] - return nil + // try to find zero value + for _, v := range JobStatusValues { + if v.Index() == 0 { + *e = v + return nil + } + } + // no zero value found, return error + return fmt.Errorf("cannot scan nil into JobStatus: no zero value defined") } str, ok := value.(string) @@ -57,19 +66,19 @@ func (e *JobStatus) Scan(value interface{}) error { return nil } +// _jobStatusParseMap is used for efficient string to enum conversion +var _jobStatusParseMap = map[string]JobStatus{ + "unknown": JobStatusUnknown, + "active": JobStatusActive, + "inactive": JobStatusInactive, + "blocked": JobStatusBlocked, +} + // ParseJobStatus converts string to jobStatus enum value func ParseJobStatus(v string) (JobStatus, error) { - switch v { - case "active": - return JobStatusActive, nil - case "blocked": - return JobStatusBlocked, nil - case "inactive": - return JobStatusInactive, nil - case "unknown": - return JobStatusUnknown, nil - + if val, ok := _jobStatusParseMap[v]; ok { + return val, nil } return JobStatus{}, fmt.Errorf("invalid jobStatus: %s", v) @@ -85,46 +94,42 @@ func MustJobStatus(v string) JobStatus { } // GetJobStatusByID gets the correspondent jobStatus enum value by its ID (raw integer value) -func GetJobStatusByID(v int) (JobStatus, error) { +func GetJobStatusByID(v uint8) (JobStatus, error) { switch v { + case 0: + return JobStatusUnknown, nil case 1: return JobStatusActive, nil - case 3: - return JobStatusBlocked, nil case 2: return JobStatusInactive, nil - case 0: - return JobStatusUnknown, nil + case 3: + return JobStatusBlocked, nil } return JobStatus{}, fmt.Errorf("invalid jobStatus value: %d", v) } // Public constants for jobStatus values var ( + JobStatusUnknown = JobStatus{name: "unknown", value: 0} JobStatusActive = JobStatus{name: "active", value: 1} - JobStatusBlocked = JobStatus{name: "blocked", value: 3} JobStatusInactive = JobStatus{name: "inactive", value: 2} - JobStatusUnknown = JobStatus{name: "unknown", value: 0} + JobStatusBlocked = JobStatus{name: "blocked", value: 3} ) -// JobStatusValues returns all possible enum values -func JobStatusValues() []JobStatus { - return []JobStatus{ - JobStatusActive, - JobStatusBlocked, - JobStatusInactive, - JobStatusUnknown, - } +// JobStatusValues contains all possible enum values +var JobStatusValues = []JobStatus{ + JobStatusUnknown, + JobStatusActive, + JobStatusInactive, + JobStatusBlocked, } -// JobStatusNames returns all possible enum names -func JobStatusNames() []string { - return []string{ - "active", - "blocked", - "inactive", - "unknown", - } +// JobStatusNames contains all possible enum names +var JobStatusNames = []string{ + "unknown", + "active", + "inactive", + "blocked", } // JobStatusIter returns a function compatible with Go 1.23's range-over-func syntax. @@ -135,7 +140,7 @@ func JobStatusNames() []string { // } func JobStatusIter() func(yield func(JobStatus) bool) { return func(yield func(JobStatus) bool) { - for _, v := range JobStatusValues() { + for _, v := range JobStatusValues { if !yield(v) { break } @@ -147,14 +152,14 @@ func JobStatusIter() func(yield func(JobStatus) bool) { // for the original enum constants. They are intentionally placed in a var block // that is compiled away by the Go compiler. var _ = func() bool { - var _ jobStatus = 0 + var _ jobStatus = jobStatus(0) + // This avoids "defined but not used" linter error for jobStatusUnknown + var _ jobStatus = jobStatusUnknown // This avoids "defined but not used" linter error for jobStatusActive var _ jobStatus = jobStatusActive - // This avoids "defined but not used" linter error for jobStatusBlocked - var _ jobStatus = jobStatusBlocked // This avoids "defined but not used" linter error for jobStatusInactive var _ jobStatus = jobStatusInactive - // This avoids "defined but not used" linter error for jobStatusUnknown - var _ jobStatus = jobStatusUnknown + // This avoids "defined but not used" linter error for jobStatusBlocked + var _ jobStatus = jobStatusBlocked return true }() diff --git a/_examples/status/job_status_test.go b/_examples/status/job_status_test.go index b6e3b53..71bd7f9 100644 --- a/_examples/status/job_status_test.go +++ b/_examples/status/job_status_test.go @@ -57,7 +57,7 @@ func TestJobStatus(t *testing.T) { // test Scan from nil - should get first value from StatusValues() err = s2.Scan(nil) require.NoError(t, err) - assert.Equal(t, JobStatusValues()[0], s2) + assert.Equal(t, JobStatusValues[0], s2) // test invalid value err = s2.Scan(123) @@ -97,7 +97,7 @@ func TestJobStatus(t *testing.T) { var s JobStatus err = db.QueryRow(`SELECT status FROM test_status WHERE id = 4`).Scan(&s) require.NoError(t, err) - assert.Equal(t, JobStatusValues()[0], s) + assert.Equal(t, JobStatusValues[0], s) }) t.Run("invalid", func(t *testing.T) { @@ -115,7 +115,7 @@ func TestJobStatus(t *testing.T) { return true }) - assert.Equal(t, JobStatusValues(), collected) + assert.Equal(t, JobStatusValues, collected) collected = nil count := 0 @@ -125,7 +125,7 @@ func TestJobStatus(t *testing.T) { return count < 2 // stop after collecting 2 items }) - assert.Equal(t, JobStatusValues()[:2], collected) + assert.Equal(t, JobStatusValues[:2], collected) }) } @@ -156,5 +156,5 @@ func ExampleJobStatusIter() { fmt.Println("first two job statuses:", firstTwo[0], firstTwo[1]) // output: // all job statuses: 4 - // first two job statuses: active blocked + // first two job statuses: unknown active } diff --git a/_examples/status/status.go b/_examples/status/status.go index 290bd49..1ac837e 100644 --- a/_examples/status/status.go +++ b/_examples/status/status.go @@ -1,7 +1,7 @@ package status -//go:generate go run ../../main.go -type status -lower -//go:generate go run ../../main.go -type jobStatus -lower -getter +//go:generate go run ../../main.go -type status -lower -sql +//go:generate go run ../../main.go -type jobStatus -lower -getter -sql type status uint8 diff --git a/_examples/status/status_enum.go b/_examples/status/status_enum.go index f0369e9..91181e3 100644 --- a/_examples/status/status_enum.go +++ b/_examples/status/status_enum.go @@ -2,19 +2,21 @@ package status import ( - "fmt" - "database/sql/driver" + "fmt" ) // Status is the exported type for the enum type Status struct { name string - value int + value uint8 } func (e Status) String() string { return e.name } +// Index returns the underlying integer value +func (e Status) Index() uint8 { return e.value } + // MarshalText implements encoding.TextMarshaler func (e Status) MarshalText() ([]byte, error) { return []byte(e.name), nil @@ -35,8 +37,15 @@ func (e Status) Value() (driver.Value, error) { // Scan implements the sql.Scanner interface func (e *Status) Scan(value interface{}) error { if value == nil { - *e = StatusValues()[0] - return nil + // try to find zero value + for _, v := range StatusValues { + if v.Index() == 0 { + *e = v + return nil + } + } + // no zero value found, return error + return fmt.Errorf("cannot scan nil into Status: no zero value defined") } str, ok := value.(string) @@ -57,19 +66,19 @@ func (e *Status) Scan(value interface{}) error { return nil } +// _statusParseMap is used for efficient string to enum conversion +var _statusParseMap = map[string]Status{ + "unknown": StatusUnknown, + "active": StatusActive, + "inactive": StatusInactive, + "blocked": StatusBlocked, +} + // ParseStatus converts string to status enum value func ParseStatus(v string) (Status, error) { - switch v { - case "active": - return StatusActive, nil - case "blocked": - return StatusBlocked, nil - case "inactive": - return StatusInactive, nil - case "unknown": - return StatusUnknown, nil - + if val, ok := _statusParseMap[v]; ok { + return val, nil } return Status{}, fmt.Errorf("invalid status: %s", v) @@ -86,30 +95,26 @@ func MustStatus(v string) Status { // Public constants for status values var ( + StatusUnknown = Status{name: "unknown", value: 0} StatusActive = Status{name: "active", value: 1} - StatusBlocked = Status{name: "blocked", value: 3} StatusInactive = Status{name: "inactive", value: 2} - StatusUnknown = Status{name: "unknown", value: 0} + StatusBlocked = Status{name: "blocked", value: 3} ) -// StatusValues returns all possible enum values -func StatusValues() []Status { - return []Status{ - StatusActive, - StatusBlocked, - StatusInactive, - StatusUnknown, - } +// StatusValues contains all possible enum values +var StatusValues = []Status{ + StatusUnknown, + StatusActive, + StatusInactive, + StatusBlocked, } -// StatusNames returns all possible enum names -func StatusNames() []string { - return []string{ - "active", - "blocked", - "inactive", - "unknown", - } +// StatusNames contains all possible enum names +var StatusNames = []string{ + "unknown", + "active", + "inactive", + "blocked", } // StatusIter returns a function compatible with Go 1.23's range-over-func syntax. @@ -120,7 +125,7 @@ func StatusNames() []string { // } func StatusIter() func(yield func(Status) bool) { return func(yield func(Status) bool) { - for _, v := range StatusValues() { + for _, v := range StatusValues { if !yield(v) { break } @@ -132,14 +137,14 @@ func StatusIter() func(yield func(Status) bool) { // for the original enum constants. They are intentionally placed in a var block // that is compiled away by the Go compiler. var _ = func() bool { - var _ status = 0 + var _ status = status(0) + // This avoids "defined but not used" linter error for statusUnknown + var _ status = statusUnknown // This avoids "defined but not used" linter error for statusActive var _ status = statusActive - // This avoids "defined but not used" linter error for statusBlocked - var _ status = statusBlocked // This avoids "defined but not used" linter error for statusInactive var _ status = statusInactive - // This avoids "defined but not used" linter error for statusUnknown - var _ status = statusUnknown + // This avoids "defined but not used" linter error for statusBlocked + var _ status = statusBlocked return true }() diff --git a/_examples/status/status_test.go b/_examples/status/status_test.go index 5ae14ef..c665575 100644 --- a/_examples/status/status_test.go +++ b/_examples/status/status_test.go @@ -55,10 +55,10 @@ func TestStatus(t *testing.T) { require.NoError(t, err) assert.Equal(t, StatusBlocked, s2) - // test Scan from nil - should get first value from StatusValues() + // test Scan from nil - should get first value from StatusValues err = s2.Scan(nil) require.NoError(t, err) - assert.Equal(t, StatusValues()[0], s2) + assert.Equal(t, StatusValues[0], s2) // test invalid value err = s2.Scan(123) @@ -98,7 +98,7 @@ func TestStatus(t *testing.T) { var s Status err = db.QueryRow(`SELECT status FROM test_status WHERE id = 4`).Scan(&s) require.NoError(t, err) - assert.Equal(t, StatusValues()[0], s) + assert.Equal(t, StatusValues[0], s) }) t.Run("iterator", func(t *testing.T) { @@ -108,7 +108,7 @@ func TestStatus(t *testing.T) { return true }) - assert.Equal(t, StatusValues(), collected) + assert.Equal(t, StatusValues, collected) collected = nil count := 0 @@ -118,7 +118,7 @@ func TestStatus(t *testing.T) { return count < 2 // stop after collecting 2 items }) - assert.Equal(t, StatusValues()[:2], collected) + assert.Equal(t, StatusValues[:2], collected) }) t.Run("invalid", func(t *testing.T) { @@ -157,5 +157,5 @@ func ExampleStatusIter() { fmt.Println("first two statuses:", firstTwo[0], firstTwo[1]) // output: // all statuses: 4 - // first two statuses: active blocked + // first two statuses: unknown active } diff --git a/enum b/enum new file mode 100755 index 0000000..3321267 Binary files /dev/null and b/enum differ diff --git a/go.mod b/go.mod index 5f0ef1c..3334791 100644 --- a/go.mod +++ b/go.mod @@ -9,6 +9,19 @@ require ( require ( github.com/davecgh/go-spew v1.1.1 // indirect + github.com/dustin/go-humanize v1.0.1 // indirect + github.com/go-pkgz/testutils v0.4.3 // indirect + github.com/google/uuid v1.6.0 // indirect + github.com/mattn/go-isatty v0.0.20 // indirect + github.com/ncruces/go-strftime v0.1.9 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect + github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec // indirect + go.mongodb.org/mongo-driver v1.17.4 // indirect + golang.org/x/exp v0.0.0-20250620022241-b7579e27df2b // indirect + golang.org/x/sys v0.34.0 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect + modernc.org/libc v1.66.3 // indirect + modernc.org/mathutil v1.7.1 // indirect + modernc.org/memory v1.11.0 // indirect + modernc.org/sqlite v1.38.2 // indirect ) diff --git a/go.sum b/go.sum index 205b534..0545ce3 100644 --- a/go.sum +++ b/go.sum @@ -1,12 +1,39 @@ github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY= +github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto= +github.com/go-pkgz/testutils v0.4.3 h1:NKYVY+/7xcn56QvLIB0ytausMO/jzwgcUA7Dfuxxkyg= +github.com/go-pkgz/testutils v0.4.3/go.mod h1:NJES7WcoqzHqKuSgBtgdCAGufEPo98qjY+ai3UoeSXY= +github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= +github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= +github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= +github.com/ncruces/go-strftime v0.1.9 h1:bY0MQC28UADQmHmaF5dgpLmImcShSi2kHU9XLdhx/f4= +github.com/ncruces/go-strftime v0.1.9/go.mod h1:Fwc5htZGVVkseilnfgOVb9mKy6w1naJmn9CehxcKcls= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec h1:W09IVJc94icq4NjY3clb7Lk8O1qJ8BdBEF8z0ibU0rE= +github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo= github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA= github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= +go.mongodb.org/mongo-driver v1.17.4 h1:jUorfmVzljjr0FLzYQsGP8cgN/qzzxlY9Vh0C9KFXVw= +go.mongodb.org/mongo-driver v1.17.4/go.mod h1:Hy04i7O2kC4RS06ZrhPRqj/u4DTYkFDAAccj+rVKqgQ= +golang.org/x/exp v0.0.0-20250620022241-b7579e27df2b h1:M2rDM6z3Fhozi9O7NWsxAkg/yqS/lQJ6PmkyIV3YP+o= +golang.org/x/exp v0.0.0-20250620022241-b7579e27df2b/go.mod h1:3//PLf8L/X+8b4vuAfHzxeRUl04Adcb341+IGKfnqS8= +golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.34.0 h1:H5Y5sJ2L2JRdyv7ROF1he/lPdvFsd0mJHFw2ThKHxLA= +golang.org/x/sys v0.34.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k= golang.org/x/text v0.28.0 h1:rhazDwis8INMIwQ4tpjLDzUhx6RlXqZNPEM0huQojng= golang.org/x/text v0.28.0/go.mod h1:U8nCwOR8jO/marOQ0QbDiOngZVEBB7MAiitBuMjXiNU= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +modernc.org/libc v1.66.3 h1:cfCbjTUcdsKyyZZfEUKfoHcP3S0Wkvz3jgSzByEWVCQ= +modernc.org/libc v1.66.3/go.mod h1:XD9zO8kt59cANKvHPXpx7yS2ELPheAey0vjIuZOhOU8= +modernc.org/mathutil v1.7.1 h1:GCZVGXdaN8gTqB1Mf/usp1Y/hSqgI2vAGGP4jZMCxOU= +modernc.org/mathutil v1.7.1/go.mod h1:4p5IwJITfppl0G4sUEDtCr4DthTaT47/N3aT6MhfgJg= +modernc.org/memory v1.11.0 h1:o4QC8aMQzmcwCK3t3Ux/ZHmwFPzE6hf2Y5LbkRs+hbI= +modernc.org/memory v1.11.0/go.mod h1:/JP4VbVC+K5sU2wZi9bHoq2MAkCnrt2r98UGeSK7Mjw= +modernc.org/sqlite v1.38.2 h1:Aclu7+tgjgcQVShZqim41Bbw9Cho0y/7WzYptXqkEek= +modernc.org/sqlite v1.38.2/go.mod h1:cPTJYSlgg3Sfg046yBShXENNtPrWrDX8bsbAQBzgQ5E= diff --git a/go.work.sum b/go.work.sum index d083c3a..c9e753a 100644 --- a/go.work.sum +++ b/go.work.sum @@ -1,36 +1,249 @@ +dario.cat/mergo v1.0.2 h1:85+piFYR1tMbRrLcDwR18y4UKJ3aH1Tbzi24VRW1TK8= +dario.cat/mergo v1.0.2/go.mod h1:E/hbnu0NxMFBjpMIE34DRGLWqDy0g5FuKDhCb31ngxA= +filippo.io/edwards25519 v1.1.0 h1:FNf4tywRC1HmFuKW5xopWpigGjJKiJSV0Cqo0cJWDaA= +filippo.io/edwards25519 v1.1.0/go.mod h1:BxyFTGdWcka3PhytdK4V28tE5sGfRvvvRV7EaN4VDT4= +github.com/Azure/go-ansiterm v0.0.0-20250102033503-faa5f7b0171c h1:udKWzYgxTojEKWjV8V+WSxDXJ4NFATAsZjh8iIbsQIg= +github.com/Azure/go-ansiterm v0.0.0-20250102033503-faa5f7b0171c/go.mod h1:xomTg63KZ2rFqZQzSB4Vz2SUXa1BpHTVz9L5PTmPC4E= +github.com/Microsoft/go-winio v0.6.2 h1:F2VQgta7ecxGYO8k3ZZz3RS8fVIXVxONVUPlNERoyfY= +github.com/Microsoft/go-winio v0.6.2/go.mod h1:yd8OoFMLzJbo9gZq8j5qaps8bJ9aShtEA8Ipt1oGCvU= +github.com/aws/aws-sdk-go-v2 v1.36.3 h1:mJoei2CxPutQVxaATCzDUjcZEjVRdpsiiXi2o38yqWM= +github.com/aws/aws-sdk-go-v2 v1.36.3/go.mod h1:LLXuLpgzEbD766Z5ECcRmi8AzSwfZItDtmABVkRLGzg= +github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.6.10 h1:zAybnyUQXIZ5mok5Jqwlf58/TFE7uvd3IAsa1aF9cXs= +github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.6.10/go.mod h1:qqvMj6gHLR/EXWZw4ZbqlPbQUyenf4h82UQUlKc+l14= +github.com/aws/aws-sdk-go-v2/config v1.29.14 h1:f+eEi/2cKCg9pqKBoAIwRGzVb70MRKqWX4dg1BDcSJM= +github.com/aws/aws-sdk-go-v2/config v1.29.14/go.mod h1:wVPHWcIFv3WO89w0rE10gzf17ZYy+UVS1Geq8Iei34g= +github.com/aws/aws-sdk-go-v2/credentials v1.17.67 h1:9KxtdcIA/5xPNQyZRgUSpYOE6j9Bc4+D7nZua0KGYOM= +github.com/aws/aws-sdk-go-v2/credentials v1.17.67/go.mod h1:p3C44m+cfnbv763s52gCqrjaqyPikj9Sg47kUVaNZQQ= +github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.16.30 h1:x793wxmUWVDhshP8WW2mlnXuFrO4cOd3HLBroh1paFw= +github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.16.30/go.mod h1:Jpne2tDnYiFascUEs2AWHJL9Yp7A5ZVy3TNyxaAjD6M= +github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.34 h1:ZK5jHhnrioRkUNOc+hOgQKlUL5JeC3S6JgLxtQ+Rm0Q= +github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.34/go.mod h1:p4VfIceZokChbA9FzMbRGz5OV+lekcVtHlPKEO0gSZY= +github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.34 h1:SZwFm17ZUNNg5Np0ioo/gq8Mn6u9w19Mri8DnJ15Jf0= +github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.34/go.mod h1:dFZsC0BLo346mvKQLWmoJxT+Sjp+qcVR1tRVHQGOH9Q= +github.com/aws/aws-sdk-go-v2/internal/ini v1.8.3 h1:bIqFDwgGXXN1Kpp99pDOdKMTTb5d2KyU5X/BZxjOkRo= +github.com/aws/aws-sdk-go-v2/internal/ini v1.8.3/go.mod h1:H5O/EsxDWyU+LP/V8i5sm8cxoZgc2fdNR9bxlOFrQTo= +github.com/aws/aws-sdk-go-v2/internal/v4a v1.3.34 h1:ZNTqv4nIdE/DiBfUUfXcLZ/Spcuz+RjeziUtNJackkM= +github.com/aws/aws-sdk-go-v2/internal/v4a v1.3.34/go.mod h1:zf7Vcd1ViW7cPqYWEHLHJkS50X0JS2IKz9Cgaj6ugrs= +github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.12.3 h1:eAh2A4b5IzM/lum78bZ590jy36+d/aFLgKF/4Vd1xPE= +github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.12.3/go.mod h1:0yKJC/kb8sAnmlYa6Zs3QVYqaC8ug2AbnNChv5Ox3uA= +github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.7.2 h1:BCG7DCXEXpNCcpwCxg1oi9pkJWH2+eZzTn9MY56MbVw= +github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.7.2/go.mod h1:iu6FSzgt+M2/x3Dk8zhycdIcHjEFb36IS8HVUVFoMg0= +github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.12.15 h1:dM9/92u2F1JbDaGooxTq18wmmFzbJRfXfVfy96/1CXM= +github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.12.15/go.mod h1:SwFBy2vjtA0vZbjjaFtfN045boopadnoVPhu4Fv66vY= +github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.18.15 h1:moLQUoVq91LiqT1nbvzDukyqAlCv89ZmwaHw/ZFlFZg= +github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.18.15/go.mod h1:ZH34PJUc8ApjBIfgQCFvkWcUDBtl/WTD+uiYHjd8igA= +github.com/aws/aws-sdk-go-v2/service/s3 v1.79.4 h1:4yxno6bNHkekkfqG/a1nz/gC2gBwhJSojV1+oTE7K+4= +github.com/aws/aws-sdk-go-v2/service/s3 v1.79.4/go.mod h1:qbn305Je/IofWBJ4bJz/Q7pDEtnnoInw/dGt71v6rHE= +github.com/aws/aws-sdk-go-v2/service/sso v1.25.3 h1:1Gw+9ajCV1jogloEv1RRnvfRFia2cL6c9cuKV2Ps+G8= +github.com/aws/aws-sdk-go-v2/service/sso v1.25.3/go.mod h1:qs4a9T5EMLl/Cajiw2TcbNt2UNo/Hqlyp+GiuG4CFDI= +github.com/aws/aws-sdk-go-v2/service/ssooidc v1.30.1 h1:hXmVKytPfTy5axZ+fYbR5d0cFmC3JvwLm5kM83luako= +github.com/aws/aws-sdk-go-v2/service/ssooidc v1.30.1/go.mod h1:MlYRNmYu/fGPoxBQVvBYr9nyr948aY/WLUvwBMBJubs= +github.com/aws/aws-sdk-go-v2/service/sts v1.33.19 h1:1XuUZ8mYJw9B6lzAkXhqHlJd/XvaX32evhproijJEZY= +github.com/aws/aws-sdk-go-v2/service/sts v1.33.19/go.mod h1:cQnB8CUnxbMU82JvlqjKR2HBOm3fe9pWorWBza6MBJ4= +github.com/aws/smithy-go v1.22.3 h1:Z//5NuZCSW6R4PhQ93hShNbyBbn8BWCmCVCt+Q8Io5k= +github.com/aws/smithy-go v1.22.3/go.mod h1:t1ufH5HMublsJYulve2RKmHDC15xu1f26kHCp/HgceI= +github.com/cenkalti/backoff/v4 v4.3.0 h1:MyRJ/UdXutAwSAT+s3wNd7MfTIcy71VQueUuFK343L8= +github.com/cenkalti/backoff/v4 v4.3.0/go.mod h1:Y3VNntkOUPxTVeUxJ/G5vcM//AlwfmyYozVcomhLiZE= +github.com/chzyer/readline v1.5.1 h1:upd/6fQk4src78LMRzh5vItIt361/o4uq553V8B5sGI= +github.com/chzyer/readline v1.5.1/go.mod h1:Eh+b79XXUwfKfcPLepksvw2tcLE/Ct21YObkaSkeBlk= +github.com/containerd/log v0.1.0 h1:TCJt7ioM2cr/tfR8GPbGf9/VRAX8D2B4PjzCpfX540I= +github.com/containerd/log v0.1.0/go.mod h1:VRRf09a7mHDIRezVKTRCrOq78v577GXq3bSa3EhrzVo= +github.com/containerd/platforms v0.2.1 h1:zvwtM3rz2YHPQsF2CHYM8+KtB5dvhISiXh5ZpSBQv6A= +github.com/containerd/platforms v0.2.1/go.mod h1:XHCb+2/hzowdiut9rkudds9bE5yJ7npe7dG/wG+uFPw= +github.com/cpuguy83/dockercfg v0.3.2 h1:DlJTyZGBDlXqUZ2Dk2Q3xHs/FtnooJJVaad2S9GKorA= +github.com/cpuguy83/dockercfg v0.3.2/go.mod h1:sugsbF4//dDlL/i+S+rtpIWp+5h0BHJHfjj5/jFyUJc= +github.com/distribution/reference v0.6.0 h1:0IXCQ5g4/QMHHkarYzh5l+u8T3t73zM5QvfrDyIgxBk= +github.com/distribution/reference v0.6.0/go.mod h1:BbU0aIcezP1/5jX/8MP0YiH4SdvB5Y4f/wlDRiLyi3E= +github.com/docker/docker v28.1.1+incompatible h1:49M11BFLsVO1gxY9UX9p/zwkE/rswggs8AdFmXQw51I= +github.com/docker/docker v28.1.1+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= +github.com/docker/go-connections v0.5.0 h1:USnMq7hx7gwdVZq1L49hLXaFtUdTADjXGp+uj1Br63c= +github.com/docker/go-connections v0.5.0/go.mod h1:ov60Kzw0kKElRwhNs9UlUHAE/F9Fe6GLaXnqyDdmEXc= +github.com/docker/go-units v0.5.0 h1:69rxXcBk27SvSaaxTtLh/8llcHD8vYHT7WSdRZ/jvr4= +github.com/docker/go-units v0.5.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk= +github.com/ebitengine/purego v0.8.4 h1:CF7LEKg5FFOsASUj0+QwaXf8Ht6TlFxg09+S9wz0omw= +github.com/ebitengine/purego v0.8.4/go.mod h1:iIjxzd6CiRiOG0UyXP+V1+jWqUXVjPKLAI0mRfJZTmQ= +github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg= +github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U= +github.com/go-logr/logr v1.4.2 h1:6pFjapn8bFcIbiKo3XT4j/BhANplGihG6tvd+8rYgrY= +github.com/go-logr/logr v1.4.2/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= +github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= +github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= +github.com/go-ole/go-ole v1.3.0 h1:Dt6ye7+vXGIKZ7Xtk4s6/xVdGDQynvom7xCFEdWr6uE= +github.com/go-ole/go-ole v1.3.0/go.mod h1:5LS6F96DhAwUc7C+1HLexzMXY1xGRSryjyPPKW6zv78= +github.com/go-sql-driver/mysql v1.9.2 h1:4cNKDYQ1I84SXslGddlsrMhc8k4LeDVj6Ad6WRjiHuU= +github.com/go-sql-driver/mysql v1.9.2/go.mod h1:qn46aNg1333BRMNU69Lq93t8du/dwxI64Gl8i5p1WMU= +github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q= +github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= +github.com/golang/snappy v1.0.0 h1:Oy607GVXHs7RtbggtPBnr2RmDArIsAefDwvrdWvRhGs= +github.com/golang/snappy v1.0.0/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= github.com/google/go-cmp v0.5.8 h1:e6P7q2lk1O+qJJb4BtCQXlK8vWEO8V1ZeuEdJNOqZyg= github.com/google/go-cmp v0.5.8/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= +github.com/google/pprof v0.0.0-20250317173921-a4b03ec1a45e h1:ijClszYn+mADRFY17kjQEVQ1XRhq2/JR1M3sGqeJoxs= +github.com/google/pprof v0.0.0-20250317173921-a4b03ec1a45e/go.mod h1:boTsfXsheKC2y+lKOCMpSfarhxDeIzfZG1jqGcPl3cA= +github.com/hashicorp/errwrap v1.1.0 h1:OxrOeh75EUXMY8TBjag2fzXGZ40LB6IKw45YeGUDY2I= +github.com/hashicorp/errwrap v1.1.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= +github.com/hashicorp/go-multierror v1.1.1 h1:H5DkEtf6CXdFp0N0Em5UCwQpXMWke8IA0+lD48awMYo= +github.com/hashicorp/go-multierror v1.1.1/go.mod h1:iw975J/qwKPdAO1clOe2L8331t/9/fmwbPZ6JB6eMoM= +github.com/ianlancetaylor/demangle v0.0.0-20240312041847-bd984b5ce465 h1:KwWnWVWCNtNq/ewIX7HIKnELmEx2nDP42yskD/pi7QE= +github.com/ianlancetaylor/demangle v0.0.0-20240312041847-bd984b5ce465/go.mod h1:gx7rwoVhcfuVKG5uya9Hs3Sxj7EIvldVofAWIUtGouw= +github.com/jlaffaye/ftp v0.2.0 h1:lXNvW7cBu7R/68bknOX3MrRIIqZ61zELs1P2RAiA3lg= +github.com/jlaffaye/ftp v0.2.0/go.mod h1:is2Ds5qkhceAPy2xD6RLI6hmp/qysSoymZ+Z2uTnspI= github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51 h1:Z9n2FFNUXsshfwJMBgNA0RU6/i7WVaAegv3PtuIHPMs= github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51/go.mod h1:CzGEWj7cYgsdH8dAjBGEr58BoE7ScuLd+fwFZ44+/x8= +github.com/klauspost/compress v1.18.0 h1:c/Cqfb0r+Yi+JtIEq73FWXVkRonBlf0CRNYc8Zttxdo= +github.com/klauspost/compress v1.18.0/go.mod h1:2Pp+KzxcywXVXMr50+X0Q/Lsb43OQHYWRCY2AiWywWQ= +github.com/kr/fs v0.1.0 h1:Jskdu9ieNAYnjxsi0LbQp1ulIKZV1LAFgK1tWhpZgl8= +github.com/kr/fs v0.1.0/go.mod h1:FFnZGqtBN9Gxj7eW1uZ42v5BccTP0vu6NEaFoC2HwRg= +github.com/lib/pq v1.10.9 h1:YXG7RB+JIjhP29X+OtkiDnYaXQwpS4JEWq7dtCCRUEw= +github.com/lib/pq v1.10.9/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o= +github.com/lufia/plan9stats v0.0.0-20250317134145-8bc96cf8fc35 h1:PpXWgLPs+Fqr325bN2FD2ISlRRztXibcX6e8f5FR5Dc= +github.com/lufia/plan9stats v0.0.0-20250317134145-8bc96cf8fc35/go.mod h1:autxFIvghDt3jPTLoqZ9OZ7s9qTGNAWmYCjVFWPX/zg= +github.com/magiconair/properties v1.8.10 h1:s31yESBquKXCV9a/ScB3ESkOjUYYv+X0rg8SYxI99mE= +github.com/magiconair/properties v1.8.10/go.mod h1:Dhd985XPs7jluiymwWYZ0G4Z61jb3vdS329zhj2hYo0= +github.com/moby/docker-image-spec v1.3.1 h1:jMKff3w6PgbfSa69GfNg+zN/XLhfXJGnEx3Nl2EsFP0= +github.com/moby/docker-image-spec v1.3.1/go.mod h1:eKmb5VW8vQEh/BAr2yvVNvuiJuY6UIocYsFu/DxxRpo= +github.com/moby/go-archive v0.1.0 h1:Kk/5rdW/g+H8NHdJW2gsXyZ7UnzvJNOy6VKJqueWdcQ= +github.com/moby/go-archive v0.1.0/go.mod h1:G9B+YoujNohJmrIYFBpSd54GTUB4lt9S+xVQvsJyFuo= +github.com/moby/patternmatcher v0.6.0 h1:GmP9lR19aU5GqSSFko+5pRqHi+Ohk1O69aFiKkVGiPk= +github.com/moby/patternmatcher v0.6.0/go.mod h1:hDPoyOpDY7OrrMDLaYoY3hf52gNCR/YOUYxkhApJIxc= +github.com/moby/sys/sequential v0.6.0 h1:qrx7XFUd/5DxtqcoH1h438hF5TmOvzC/lspjy7zgvCU= +github.com/moby/sys/sequential v0.6.0/go.mod h1:uyv8EUTrca5PnDsdMGXhZe6CCe8U/UiTWd+lL+7b/Ko= +github.com/moby/sys/user v0.4.0 h1:jhcMKit7SA80hivmFJcbB1vqmw//wU61Zdui2eQXuMs= +github.com/moby/sys/user v0.4.0/go.mod h1:bG+tYYYJgaMtRKgEmuueC0hJEAZWwtIbZTB+85uoHjs= +github.com/moby/sys/userns v0.1.0 h1:tVLXkFOxVu9A64/yh59slHVv9ahO9UIev4JZusOLG/g= +github.com/moby/sys/userns v0.1.0/go.mod h1:IHUYgu/kao6N8YZlp9Cf444ySSvCmDlmzUcYfDHOl28= +github.com/moby/term v0.5.2 h1:6qk3FJAFDs6i/q3W/pQ97SX192qKfZgGjCQqfCJkgzQ= +github.com/moby/term v0.5.2/go.mod h1:d3djjFCrjnB+fl8NJux+EJzu0msscUP+f8it8hPkFLc= +github.com/montanaflynn/stats v0.7.1 h1:etflOAAHORrCC44V+aR6Ftzort912ZU+YLiSTuV8eaE= +github.com/montanaflynn/stats v0.7.1/go.mod h1:etXPPgVO6n31NxCd9KQUMvCM+ve0ruNzt6R8Bnaayow= +github.com/morikuni/aec v1.0.0 h1:nP9CBfwrvYnBRgY6qfDQkygYDmYwOilePFkwzv4dU8A= +github.com/morikuni/aec v1.0.0/go.mod h1:BbKIizmSmc5MMPqRYbxO4ZU0S0+P200+tUnFx7PXmsc= +github.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8Oi/yOhh5U= +github.com/opencontainers/go-digest v1.0.0/go.mod h1:0JzlMkj0TRzQZfJkVvzbP0HBR3IKzErnv2BNG4W4MAM= +github.com/opencontainers/image-spec v1.1.1 h1:y0fUlFfIZhPF1W537XOLg0/fcx6zcHCJwooC2xJA040= +github.com/opencontainers/image-spec v1.1.1/go.mod h1:qpqAh3Dmcf36wStyyWU+kCeDgrGnAve2nCC8+7h8Q0M= +github.com/pbnjay/memory v0.0.0-20210728143218-7b4eea64cf58 h1:onHthvaw9LFnH4t2DcNVpwGmV9E1BkGknEliJkfwQj0= +github.com/pbnjay/memory v0.0.0-20210728143218-7b4eea64cf58/go.mod h1:DXv8WO4yhMYhSNPKjeNKa5WY9YCIEBRbNzFFPJbWO6Y= +github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= +github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/pkg/sftp v1.13.9 h1:4NGkvGudBL7GteO3m6qnaQ4pC0Kvf0onSVc9gR3EWBw= +github.com/pkg/sftp v1.13.9/go.mod h1:OBN7bVXdstkFFN/gdnHPUb5TE8eb8G1Rp9wCItqjkkA= +github.com/power-devops/perfstat v0.0.0-20240221224432-82ca36839d55 h1:o4JXh1EVt9k/+g42oCprj/FisM4qX9L3sZB3upGN2ZU= +github.com/power-devops/perfstat v0.0.0-20240221224432-82ca36839d55/go.mod h1:OmDBASR4679mdNQnz2pUhc2G8CO2JrUAVFDRBDP/hJE= +github.com/shirou/gopsutil/v4 v4.25.4 h1:cdtFO363VEOOFrUCjZRh4XVJkb548lyF0q0uTeMqYPw= +github.com/shirou/gopsutil/v4 v4.25.4/go.mod h1:xbuxyoZj+UsgnZrENu3lQivsngRR5BdjbJwf2fv4szA= +github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ= +github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= github.com/stretchr/objx v0.5.2 h1:xuMeJ0Sdp5ZMRXx/aWO6RZxdr3beISkG5/G/aIRr3pY= github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA= +github.com/testcontainers/testcontainers-go v0.37.0 h1:L2Qc0vkTw2EHWQ08djon0D2uw7Z/PtHS/QzZZ5Ra/hg= +github.com/testcontainers/testcontainers-go v0.37.0/go.mod h1:QPzbxZhQ6Bclip9igjLFj6z0hs01bU8lrl2dHQmgFGM= +github.com/tklauser/go-sysconf v0.3.15 h1:VE89k0criAymJ/Os65CSn1IXaol+1wrsFHEB8Ol49K4= +github.com/tklauser/go-sysconf v0.3.15/go.mod h1:Dmjwr6tYFIseJw7a3dRLJfsHAMXZ3nEnL/aZY+0IuI4= +github.com/tklauser/numcpus v0.10.0 h1:18njr6LDBk1zuna922MgdjQuJFjrdppsZG60sHGfjso= +github.com/tklauser/numcpus v0.10.0/go.mod h1:BiTKazU708GQTYF4mB+cmlpT2Is1gLk7XVuEeem8LsQ= +github.com/xdg-go/pbkdf2 v1.0.0 h1:Su7DPu48wXMwC3bs7MCNG+z4FhcyEuz5dlvchbq0B0c= +github.com/xdg-go/pbkdf2 v1.0.0/go.mod h1:jrpuAogTd400dnrH08LKmI/xc1MbPOebTwRqcT5RDeI= +github.com/xdg-go/scram v1.1.2 h1:FHX5I5B4i4hKRVRBCFRxq1iQRej7WO3hhBuJf+UUySY= +github.com/xdg-go/scram v1.1.2/go.mod h1:RT/sEzTbU5y00aCK8UOx6R7YryM0iF1N2MOmC3kKLN4= +github.com/xdg-go/stringprep v1.0.4 h1:XLI/Ng3O1Atzq0oBs3TWm+5ZVgkq2aqdlvP9JtoZ6c8= +github.com/xdg-go/stringprep v1.0.4/go.mod h1:mPGuuIYwz7CmR2bT9j4GbQqutWS1zV24gijq1dTyGkM= +github.com/youmark/pkcs8 v0.0.0-20240726163527-a2c0da244d78 h1:ilQV1hzziu+LLM3zUTJ0trRztfwgjqKnBWNtSRkbmwM= +github.com/youmark/pkcs8 v0.0.0-20240726163527-a2c0da244d78/go.mod h1:aL8wCCfTfSfmXjznFBSZNN13rSJjlIOI1fUNAtF7rmI= github.com/yuin/goldmark v1.4.13 h1:fVcFKWvrslecOb/tg+Cc05dkeYx540o0FuFt3nUVDoE= github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= +github.com/yusufpapurcu/wmi v1.2.4 h1:zFUKzehAFReQwLys1b/iSMl+JQGSCSjtVqQn9bBrPo0= +github.com/yusufpapurcu/wmi v1.2.4/go.mod h1:SBZ9tNy3G9/m5Oi98Zks0QjeHVDvuK0qfxQmPyzfmi0= +go.mongodb.org/mongo-driver v1.17.3 h1:TQyXhnsWfWtgAhMtOgtYHMTkZIfBTpMTsMnd9ZBeHxQ= +go.mongodb.org/mongo-driver v1.17.3/go.mod h1:Hy04i7O2kC4RS06ZrhPRqj/u4DTYkFDAAccj+rVKqgQ= +go.opentelemetry.io/auto/sdk v1.1.0 h1:cH53jehLUN6UFLY71z+NDOiNJqDdPRaXzTel0sJySYA= +go.opentelemetry.io/auto/sdk v1.1.0/go.mod h1:3wSPjt5PWp2RhlCcmmOial7AvC4DQqZb7a7wCow3W8A= +go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.61.0 h1:F7Jx+6hwnZ41NSFTO5q4LYDtJRXBf2PD0rNBkeB/lus= +go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.61.0/go.mod h1:UHB22Z8QsdRDrnAtX4PntOl36ajSxcdUMt1sF7Y6E7Q= +go.opentelemetry.io/otel v1.36.0 h1:UumtzIklRBY6cI/lllNZlALOF5nNIzJVb16APdvgTXg= +go.opentelemetry.io/otel v1.36.0/go.mod h1:/TcFMXYjyRNh8khOAO9ybYkqaDBb/70aVwkNML4pP8E= +go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.19.0 h1:Mne5On7VWdx7omSrSSZvM4Kw7cS7NQkOOmLcgscI51U= +go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.19.0/go.mod h1:IPtUMKL4O3tH5y+iXVyAXqpAwMuzC1IrxVS81rummfE= +go.opentelemetry.io/otel/metric v1.36.0 h1:MoWPKVhQvJ+eeXWHFBOPoBOi20jh6Iq2CcCREuTYufE= +go.opentelemetry.io/otel/metric v1.36.0/go.mod h1:zC7Ks+yeyJt4xig9DEw9kuUFe5C3zLbVjV2PzT6qzbs= +go.opentelemetry.io/otel/trace v1.36.0 h1:ahxWNuqZjpdiFAyrIoQ4GIiAIhxAunQR6MUoKrsNd4w= +go.opentelemetry.io/otel/trace v1.36.0/go.mod h1:gQ+OnDZzrybY4k4seLzPAWNwVBBVlF2szhehOBB/tGA= +go.opentelemetry.io/proto/otlp v1.0.0 h1:T0TX0tmXU8a3CbNXzEKGeU5mIVOdf0oykP+u2lIVU/I= +go.opentelemetry.io/proto/otlp v1.0.0/go.mod h1:Sy6pihPLfYHkr3NkUbEhGHFhINUSI/v80hjKIs5JXpM= +golang.org/x/crypto v0.38.0 h1:jt+WWG8IZlBnVbomuhg2Mdq0+BBQaHbtqHEFEigjUV8= +golang.org/x/crypto v0.38.0/go.mod h1:MvrbAqul58NNYPKnOra203SB9vpuZW0e+RRZV+Ggqjw= +golang.org/x/crypto v0.40.0 h1:r4x+VvoG5Fm+eJcxMaY8CQM7Lb0l1lsmjGBQ6s8BfKM= +golang.org/x/crypto v0.40.0/go.mod h1:Qr1vMER5WyS2dfPHAlsOj01wgLbsyWtFn/aY+5+ZdxY= golang.org/x/mod v0.17.0 h1:zY54UmvipHiNd+pm+m0x9KhZ9hl1/7QNMyxXbc6ICqA= golang.org/x/mod v0.17.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= +golang.org/x/mod v0.25.0/go.mod h1:IXM97Txy2VM4PJ3gI61r1YEk/gAj6zAHN3AdZt6S9Ww= golang.org/x/mod v0.26.0 h1:EGMPT//Ezu+ylkCijjPc+f4Aih7sZvaAr+O3EHBxvZg= golang.org/x/mod v0.26.0/go.mod h1:/j6NAhSk8iQ723BGAUyoAcn7SlD7s15Dp9Nd/SfeaFQ= +golang.org/x/net v0.21.0 h1:AQyQV4dYCvJ7vGmJyKki9+PBdyvhkSd8EIx/qb0AYv4= +golang.org/x/net v0.21.0/go.mod h1:bIjVDfnllIU7BJ2DNgfnXvpSvtn8VRwhlsaeUTyUS44= golang.org/x/net v0.35.0 h1:T5GQRQb2y08kTAByq9L4/bz8cipCdA8FbRTXewonqY8= golang.org/x/net v0.35.0/go.mod h1:EglIi67kWsHKlRzzVMUD93VMSWGFOMSZgxFjparz1Qk= +golang.org/x/net v0.39.0 h1:ZCu7HMWDxpXpaiKdhzIfaltL9Lp31x/3fCP11bc6/fY= +golang.org/x/net v0.39.0/go.mod h1:X7NRbYVEA+ewNkCNyJ513WmMdQ3BineSwVtN2zD/d+E= +golang.org/x/net v0.42.0 h1:jzkYrhi3YQWD6MLBJcsklgQsoAcw89EcZbJw8Z614hs= +golang.org/x/net v0.42.0/go.mod h1:FF1RA5d3u7nAYA4z2TkclSCKh68eSXtiFwcWQpPXdt8= golang.org/x/sync v0.11.0 h1:GGz8+XQP4FvTTrjZPzNKTMFtSXH80RAzG+5ghFPgK9w= golang.org/x/sync v0.11.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= golang.org/x/sync v0.12.0 h1:MHc5BpPuC30uJk597Ri8TV3CNZcTLu6B6z4lJy+g6Jw= golang.org/x/sync v0.12.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA= +golang.org/x/sync v0.15.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA= golang.org/x/sync v0.16.0 h1:ycBJEhp9p4vXvUZNszeOq0kGTPghopOL8q0fq3vstxw= golang.org/x/sync v0.16.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA= golang.org/x/sys v0.30.0 h1:QjkSwP/36a20jFYWkSue1YwXzLmsV5Gfq7Eiy72C1uc= golang.org/x/sys v0.30.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.31.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k= +golang.org/x/sys v0.33.0 h1:q3i8TbbEz+JRD9ywIRlyRAQbM0qF7hu24q3teo2hbuw= +golang.org/x/sys v0.33.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k= golang.org/x/telemetry v0.0.0-20240521205824-bda55230c457 h1:zf5N6UOrA487eEFacMePxjXAJctxKmyjKUsjA11Uzuk= golang.org/x/telemetry v0.0.0-20240521205824-bda55230c457/go.mod h1:pRgIJT+bRLFKnoM1ldnzKoxTIn14Yxz928LQRYYgIN0= +golang.org/x/telemetry v0.0.0-20250710130107-8d8967aff50b h1:DU+gwOBXU+6bO0sEyO7o/NeMlxZxCZEvI7v+J4a1zRQ= +golang.org/x/telemetry v0.0.0-20250710130107-8d8967aff50b/go.mod h1:4ZwOYna0/zsOKwuR5X/m0QFOJpSZvAxFfkQT+Erd9D4= +golang.org/x/term v0.23.0 h1:F6D4vR+EHoL9/sWAWgAR1H2DcHr4PareCbAaCo1RpuU= +golang.org/x/term v0.23.0/go.mod h1:DgV24QBUrK6jhZXl+20l6UWznPlwAHm1Q1mGHtydmSk= +golang.org/x/term v0.32.0 h1:DR4lr0TjUs3epypdhTOkMmuF5CDFJ/8pOnbzMZPQ7bg= +golang.org/x/term v0.32.0/go.mod h1:uZG1FhGx848Sqfsq4/DlJr3xGGsYMu/L5GW4abiaEPQ= +golang.org/x/term v0.33.0 h1:NuFncQrRcaRvVmgRkvM3j/F00gWIAlcmlB8ACEKmGIg= +golang.org/x/term v0.33.0/go.mod h1:s18+ql9tYWp1IfpV9DmCtQDDSRBUjKaw9M1eAv5UeF0= golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d h1:vU5i/LfpvrRCpgM/VPfJLg5KjxD3E+hfT1SH+d9zLwg= golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d/go.mod h1:aiJjzUbINMkxbQROHiO6hDPo2LHcIPhhQsa9DLh0yGk= +golang.org/x/tools v0.34.0/go.mod h1:pAP9OwEaY1CAW3HOmg3hLZC5Z0CCmzjAF2UQMSqNARg= golang.org/x/tools v0.35.0 h1:mBffYraMEf7aa0sB+NuKnuCy8qI/9Bughn8dC2Gu5r0= golang.org/x/tools v0.35.0/go.mod h1:NKdj5HkL/73byiZSJjqJgKn3ep7KjFkBOkR/Hps3VPw= +golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7 h1:9zdDQZ7Thm29KFXgAX/+yaf3eVbP7djjWp/dXAppNCc= +google.golang.org/genproto/googleapis/api v0.0.0-20231120223509-83a465c0220f h1:2yNACc1O40tTnrsbk9Cv6oxiW8pxI/pXj0wRtdlYmgY= +google.golang.org/genproto/googleapis/api v0.0.0-20231120223509-83a465c0220f/go.mod h1:Uy9bTZJqmfrw2rIBxgGLnamc78euZULUBrLZ9XTITKI= +google.golang.org/genproto/googleapis/rpc v0.0.0-20240401170217-c3f982113cda h1:LI5DOvAxUPMv/50agcLLoo+AdWc1irS9Rzz4vPuD1V4= +google.golang.org/genproto/googleapis/rpc v0.0.0-20240401170217-c3f982113cda/go.mod h1:WtryC6hu0hhx87FDGxWCDptyssuo68sk10vYjF+T9fY= +google.golang.org/protobuf v1.35.2 h1:8Ar7bF+apOIoThw1EdZl0p1oWvMqTHmpA2fRTyZO8io= +google.golang.org/protobuf v1.35.2/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE= lukechampine.com/uint128 v1.2.0 h1:mBi/5l91vocEN8otkC5bDLhi2KdCticRiwbdB0O+rjI= lukechampine.com/uint128 v1.2.0/go.mod h1:c4eWIwlEGaxC/+H1VguhU4PHXNWDCDMUlWdIWl2j1gk= modernc.org/cc/v3 v3.41.0 h1:QoR1Sn3YWlmA1T4vLaKZfawdVtSiGx8H+cEojbC7v1Q= modernc.org/cc/v3 v3.41.0/go.mod h1:Ni4zjJYJ04CDOhG7dn640WGfwBzfE0ecX8TyMB0Fv0Y= +modernc.org/cc/v4 v4.26.2 h1:991HMkLjJzYBIfha6ECZdjrIYz2/1ayr+FL8GN+CNzM= +modernc.org/cc/v4 v4.26.2/go.mod h1:uVtb5OGqUKpoLWhqwNQo/8LwvoiEBLvZXIQ/SmO6mL0= modernc.org/ccgo/v3 v3.17.0 h1:o3OmOqx4/OFnl4Vm3G8Bgmqxnvxnh0nbxeT5p/dWChA= modernc.org/ccgo/v3 v3.17.0/go.mod h1:Sg3fwVpmLvCUTaqEUjiBDAvshIaKDB0RXaf+zgqFu8I= +modernc.org/ccgo/v4 v4.28.0 h1:rjznn6WWehKq7dG4JtLRKxb52Ecv8OUGah8+Z/SfpNU= +modernc.org/ccgo/v4 v4.28.0/go.mod h1:JygV3+9AV6SmPhDasu4JgquwU81XAKLd3OKTUDNOiKE= +modernc.org/ccorpus2 v1.5.2 h1:Ui+4tc58mf/W+2arcYCJR903y3zl3ecsI7Fpaaqozyw= +modernc.org/ccorpus2 v1.5.2/go.mod h1:Wifvo4Q/qS/h1aRoC2TffcHsnxwTikmi1AuLANuucJQ= +modernc.org/fileutil v1.3.8 h1:qtzNm7ED75pd1C7WgAGcK4edm4fvhtBsEiI/0NQ54YM= +modernc.org/fileutil v1.3.8/go.mod h1:HxmghZSZVAz/LXcMNwZPA/DRrQZEVP9VX0V4LQGQFOc= +modernc.org/gc/v2 v2.6.5 h1:nyqdV8q46KvTpZlsw66kWqwXRHdjIlJOhG6kxiV/9xI= +modernc.org/gc/v2 v2.6.5/go.mod h1:YgIahr1ypgfe7chRuJi2gD7DBQiKSLMPgBQe9oIiito= +modernc.org/goabi0 v0.2.0 h1:HvEowk7LxcPd0eq6mVOAEMai46V+i7Jrj13t4AzuNks= +modernc.org/goabi0 v0.2.0/go.mod h1:CEFRnnJhKvWT1c1JTI3Avm+tgOWbkOu5oPA8eH8LnMI= +modernc.org/lex v1.1.1 h1:prSCNTLw1R4rn7M/RzwsuMtAuOytfyR3cnyM07P+Pas= +modernc.org/lex v1.1.1/go.mod h1:6r8o8DLJkAnOsQaGi8fMoi+Vt6LTbDaCrkUK729D8xM= +modernc.org/lexer v1.0.4 h1:hU7xVbZsqwPphyzChc7nMSGrsuaD2PDNOmzrzkS5AlE= +modernc.org/lexer v1.0.4/go.mod h1:tOajb8S4sdfOYitzCgXDFmbVJ/LE0v1fNJ7annTw36U= +modernc.org/scannertest v1.0.2 h1:JPtfxcVdbRvzmRf2YUvsDibJsQRw8vKA/3jb31y7cy0= +modernc.org/scannertest v1.0.2/go.mod h1:RzTm5RwglF/6shsKoEivo8N91nQIoWtcWI7ns+zPyGA= diff --git a/internal/generator/enum.go.tmpl b/internal/generator/enum.go.tmpl index 4c96309..3b2d4fa 100644 --- a/internal/generator/enum.go.tmpl +++ b/internal/generator/enum.go.tmpl @@ -4,7 +4,16 @@ package {{.Package}} import ( "fmt" + {{- if .GenerateSQL }} "database/sql/driver" + {{- end}} + {{- if .GenerateBSON }} + "go.mongodb.org/mongo-driver/bson" + "go.mongodb.org/mongo-driver/bson/bsontype" + {{- end}} + {{- if .GenerateYAML }} + "gopkg.in/yaml.v3" + {{- end}} {{- if .LowerCase | not }} "strings" {{- end}} @@ -33,6 +42,7 @@ func (e *{{.Type | title}}) UnmarshalText(text []byte) error { return err } +{{- if .GenerateSQL }} // Value implements the driver.Valuer interface func (e {{.Type | title}}) Value() (driver.Value, error) { return e.name, nil @@ -69,6 +79,48 @@ func (e *{{.Type | title}}) Scan(value interface{}) error { *e = val return nil } +{{- end }} + +{{- if .GenerateBSON }} +// MarshalBSONValue implements bson.ValueMarshaler and encodes the enum as a string +func (e {{.Type | title}}) MarshalBSONValue() (bsontype.Type, []byte, error) { + return bson.MarshalValue(e.String()) +} + +// UnmarshalBSONValue implements bson.ValueUnmarshaler and decodes the enum from a string +func (e *{{.Type | title}}) UnmarshalBSONValue(t bsontype.Type, data []byte) error { + var s string + if err := bson.UnmarshalValue(t, data, &s); err != nil { + return err + } + val, err := Parse{{.Type | title}}(s) + if err != nil { + return err + } + *e = val + return nil +} +{{- end }} + +{{- if .GenerateYAML }} +// MarshalYAML implements yaml.Marshaler and encodes the enum as a string +func (e {{.Type | title}}) MarshalYAML() (any, error) { + return e.String(), nil +} + +// UnmarshalYAML implements yaml.Unmarshaler and decodes the enum from a string scalar +func (e *{{.Type | title}}) UnmarshalYAML(value *yaml.Node) error { + if value == nil || value.Kind != yaml.ScalarNode { + return fmt.Errorf("invalid YAML for {{.Type}}: expected scalar string") + } + val, err := Parse{{.Type | title}}(value.Value) + if err != nil { + return err + } + *e = val + return nil +} +{{- end }} // _{{.Type}}ParseMap is used for efficient string to enum conversion var _{{.Type}}ParseMap = map[string]{{.Type | title}}{ @@ -155,10 +207,10 @@ func {{.Type | title}}Iter() func(yield func({{.Type | title}}) bool) { // for the original enum constants. They are intentionally placed in a var block // that is compiled away by the Go compiler. var _ = func() bool { - var _ {{.Type}} = {{if .UnderlyingType}}{{.Type}}(0){{else}}0{{end}} - {{range .Values -}} - // This avoids "defined but not used" linter error for {{.PrivateName}} - var _ {{$.Type}} = {{.PrivateName}} - {{end -}} - return true -}() \ No newline at end of file + var _ {{.Type}} = {{if .UnderlyingType}}{{.Type}}(0){{else}}0{{end}} + {{range .Values -}} + // This avoids "defined but not used" linter error for {{.PrivateName}} + var _ {{$.Type}} = {{.PrivateName}} + {{end -}} + return true +}() diff --git a/internal/generator/generator.go b/internal/generator/generator.go index b5a3bde..3218293 100644 --- a/internal/generator/generator.go +++ b/internal/generator/generator.go @@ -1,5 +1,5 @@ // Package generator provides a code generator for enum types. It reads Go source files and extracts enum values -// to generate a new type with json, bson and text marshaling support. +// to generate a new type with text marshaling support by default. Optional flags add SQL, BSON (MongoDB), and YAML support. package generator import ( @@ -35,6 +35,9 @@ type Generator struct { lowerCase bool // use lower case for marshal/unmarshal generateGetter bool // generate getter methods for enum values underlyingType string // underlying type (e.g., "uint8", "int", etc.) + generateSQL bool // generate SQL interfaces and imports + generateBSON bool // generate BSON interfaces and imports + generateYAML bool // generate YAML interfaces and imports } // constValue holds metadata about a const during parsing @@ -102,6 +105,15 @@ func (g *Generator) SetGenerateGetter(generate bool) { g.generateGetter = generate } +// SetGenerateSQL enables or disables generation of SQL interfaces +func (g *Generator) SetGenerateSQL(v bool) { g.generateSQL = v } + +// SetGenerateBSON enables or disables generation of BSON interfaces +func (g *Generator) SetGenerateBSON(v bool) { g.generateBSON = v } + +// SetGenerateYAML enables or disables generation of YAML interfaces +func (g *Generator) SetGenerateYAML(v bool) { g.generateYAML = v } + // Parse reads the source directory and extracts enum information. it looks for const values // that start with the enum type name, for example if type is "status", it will find all const values // that start with "status". The values must use iota and be in sequence. The values map will contain @@ -237,6 +249,19 @@ func (g *Generator) processExplicitValue(expr ast.Expr, state *constParseState) state.iotaOp = nil return val } + case *ast.UnaryExpr: + // handle negative numbers like -1 + if e.Op == token.SUB { + if lit, ok := e.X.(*ast.BasicLit); ok { + if val, err := ConvertLiteralToInt(lit); err == nil { + state.lastExprType = exprTypePlain + state.lastValue = -val + state.iotaOp = nil + return -val + } + // if conversion fails, fall through to return 0 (same as BasicLit case) + } + } } return 0 } @@ -509,6 +534,9 @@ func (g *Generator) Generate() error { LowerCase bool GenerateGetter bool UnderlyingType string + GenerateSQL bool + GenerateBSON bool + GenerateYAML bool }{ Type: g.Type, Values: values, @@ -516,6 +544,9 @@ func (g *Generator) Generate() error { LowerCase: g.lowerCase, GenerateGetter: g.generateGetter, UnderlyingType: g.underlyingType, + GenerateSQL: g.generateSQL, + GenerateBSON: g.generateBSON, + GenerateYAML: g.generateYAML, } // execute template diff --git a/internal/generator/generator_test.go b/internal/generator/generator_test.go index 389b6f0..a6c435d 100644 --- a/internal/generator/generator_test.go +++ b/internal/generator/generator_test.go @@ -40,6 +40,9 @@ func TestGenerator(t *testing.T) { err = gen.Parse("testdata") require.NoError(t, err) + // enable SQL to verify SQL-specific imports/methods when requested + gen.SetGenerateSQL(true) + err = gen.Generate() require.NoError(t, err) @@ -139,6 +142,7 @@ func TestGenerator(t *testing.T) { err = gen.Parse("testdata") require.NoError(t, err) + gen.SetGenerateSQL(true) err = gen.Generate() require.NoError(t, err) @@ -160,6 +164,49 @@ func TestGenerator(t *testing.T) { assert.Contains(t, string(content), "if b, ok := value.([]byte)") }) + t.Run("bson support", func(t *testing.T) { + tmpDir := t.TempDir() + gen, err := New("status", tmpDir) + require.NoError(t, err) + + err = gen.Parse("testdata") + require.NoError(t, err) + + gen.SetGenerateBSON(true) + err = gen.Generate() + require.NoError(t, err) + + content, err := os.ReadFile(filepath.Join(tmpDir, "status_enum.go")) + require.NoError(t, err) + + // verify bson interfaces and imports + assert.Contains(t, string(content), `"go.mongodb.org/mongo-driver/bson"`) + assert.Contains(t, string(content), `"go.mongodb.org/mongo-driver/bson/bsontype"`) + assert.Contains(t, string(content), "func (e Status) MarshalBSONValue() (bsontype.Type, []byte, error)") + assert.Contains(t, string(content), "func (e *Status) UnmarshalBSONValue(t bsontype.Type, data []byte) error") + }) + + t.Run("yaml support", func(t *testing.T) { + tmpDir := t.TempDir() + gen, err := New("status", tmpDir) + require.NoError(t, err) + + err = gen.Parse("testdata") + require.NoError(t, err) + + gen.SetGenerateYAML(true) + err = gen.Generate() + require.NoError(t, err) + + content, err := os.ReadFile(filepath.Join(tmpDir, "status_enum.go")) + require.NoError(t, err) + + // verify yaml interfaces and imports + assert.Contains(t, string(content), `"gopkg.in/yaml.v3"`) + assert.Contains(t, string(content), "func (e Status) MarshalYAML() (any, error)") + assert.Contains(t, string(content), "func (e *Status) UnmarshalYAML(value *yaml.Node) error") + }) + t.Run("json support", func(t *testing.T) { tmpDir := t.TempDir() gen, err := New("status", tmpDir) @@ -178,8 +225,7 @@ func TestGenerator(t *testing.T) { assert.Contains(t, string(content), "func (e Status) MarshalText() ([]byte, error)") assert.Contains(t, string(content), "func (e *Status) UnmarshalText(text []byte) error") - // verify proper error handling in unmarshal - assert.Contains(t, string(content), "invalid status value: %v") + // verify UnmarshalText uses Parse assert.Contains(t, string(content), "ParseStatus(string(text))") // verify string conversion in marshal @@ -365,6 +411,7 @@ func TestSQLNullHandling(t *testing.T) { err = gen.Parse("testdata") require.NoError(t, err) + gen.SetGenerateSQL(true) err = gen.Generate() require.NoError(t, err) @@ -385,6 +432,7 @@ func TestSQLNullHandling(t *testing.T) { err = gen.Parse("testdata") require.NoError(t, err) + gen.SetGenerateSQL(true) err = gen.Generate() require.NoError(t, err) @@ -781,6 +829,78 @@ func TestGetFileNameForType(t *testing.T) { } } +func TestNegativeEnumValues(t *testing.T) { + t.Run("negative integers", func(t *testing.T) { + tmpDir := t.TempDir() + + // create enum with negative values + enumFile := filepath.Join(tmpDir, "test.go") + err := os.WriteFile(enumFile, []byte(`package test + +type errorCode int + +const ( + errorCodeNone errorCode = -1 + errorCodeOK errorCode = 0 + errorCodeBadRequest errorCode = 400 + errorCodeNotFound errorCode = 404 +)`), 0o644) + require.NoError(t, err) + + gen, err := New("errorCode", tmpDir) + require.NoError(t, err) + + err = gen.Parse(tmpDir) + require.NoError(t, err) + + // verify negative value was parsed correctly + assert.Equal(t, -1, gen.values["errorCodeNone"].value) + assert.Equal(t, 0, gen.values["errorCodeOK"].value) + assert.Equal(t, 400, gen.values["errorCodeBadRequest"].value) + assert.Equal(t, 404, gen.values["errorCodeNotFound"].value) + + err = gen.Generate() + require.NoError(t, err) + + // verify generated code has correct values + content, err := os.ReadFile(filepath.Join(tmpDir, "error_code_enum.go")) + require.NoError(t, err) + + contentStr := string(content) + assert.Contains(t, contentStr, "ErrorCodeNone = ErrorCode{name: \"None\", value: -1}") + assert.Contains(t, contentStr, "ErrorCodeOK = ErrorCode{name: \"OK\", value: 0}") + assert.Contains(t, contentStr, "ErrorCodeBadRequest = ErrorCode{name: \"BadRequest\", value: 400}") + assert.Contains(t, contentStr, "ErrorCodeNotFound = ErrorCode{name: \"NotFound\", value: 404}") + }) + + t.Run("invalid negative expression", func(t *testing.T) { + tmpDir := t.TempDir() + + // create enum with invalid negative expression (should default to 0) + enumFile := filepath.Join(tmpDir, "test.go") + err := os.WriteFile(enumFile, []byte(`package test + +type status int + +const ( + statusInvalid status = -"not_a_number" + statusOK status = 1 +)`), 0o644) + require.NoError(t, err) + + gen, err := New("status", tmpDir) + require.NoError(t, err) + + // this should parse but the invalid value should become 0 + err = gen.Parse(tmpDir) + require.NoError(t, err) + + // verify invalid negative expression defaulted to 0 + assert.Equal(t, 0, gen.values["statusInvalid"].value) + assert.Equal(t, 1, gen.values["statusOK"].value) + }) +} + func TestUnderlyingTypePreservation(t *testing.T) { t.Run("uint8 type", func(t *testing.T) { tmpDir := t.TempDir() diff --git a/internal/generator/integration_test.go b/internal/generator/integration_test.go new file mode 100644 index 0000000..a4810a1 --- /dev/null +++ b/internal/generator/integration_test.go @@ -0,0 +1,648 @@ +package generator + +import ( + "database/sql" + "database/sql/driver" + "encoding/json" + "os" + "os/exec" + "path/filepath" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + "go.mongodb.org/mongo-driver/bson" + "gopkg.in/yaml.v3" + _ "modernc.org/sqlite" +) + +// TestGenerateEnumWithAllFeatures generates an enum with all features and verifies the output +func TestGenerateEnumWithAllFeatures(t *testing.T) { + // use the testdata/integration directory + testDir := "testdata/integration" + + // ensure status.go exists + statusFile := filepath.Join(testDir, "status.go") + require.FileExists(t, statusFile, "testdata/integration/status.go should exist") + + // generate with all features + gen, err := New("status", testDir) + require.NoError(t, err) + gen.SetLowerCase(true) + gen.SetGenerateBSON(true) + gen.SetGenerateSQL(true) + gen.SetGenerateYAML(true) + + err = gen.Parse(testDir) + require.NoError(t, err) + + err = gen.Generate() + require.NoError(t, err) + + // verify generated file exists and contains expected methods + content, err := os.ReadFile(filepath.Join(testDir, "status_enum.go")) + require.NoError(t, err) + + // verify all features are present + assert.Contains(t, string(content), "String() string") + assert.Contains(t, string(content), "ParseStatus(") + assert.Contains(t, string(content), "MarshalText()") + assert.Contains(t, string(content), "UnmarshalText(") + assert.Contains(t, string(content), "MarshalBSONValue()") + assert.Contains(t, string(content), "UnmarshalBSONValue(") + assert.Contains(t, string(content), "Value() (driver.Value") + assert.Contains(t, string(content), "Scan(value interface{})") + assert.Contains(t, string(content), "MarshalYAML()") + assert.Contains(t, string(content), "UnmarshalYAML(") + + // don't cleanup - we need the generated file for integration tests +} + +// TestMongoDBIntegration tests MongoDB operations with containers +func TestMongoDBIntegration(t *testing.T) { + if testing.Short() { + t.Skip("skipping integration test in short mode") + } + + // check if MongoDB is available + if os.Getenv("SKIP_MONGO_TEST") != "" { + t.Skip("skipping MongoDB test") + } + + // first generate the enum + testDir := t.TempDir() + setupTestEnum(t, testDir) + + // import and use testutils for MongoDB container + // this would normally use the generated enum, but since we can't dynamically import, + // we test the generated code structure instead + content, err := os.ReadFile(filepath.Join(testDir, "status_enum.go")) + require.NoError(t, err) + + // verify BSON marshaling code is correct + assert.Contains(t, string(content), "func (e Status) MarshalBSONValue() (bsontype.Type, []byte, error)") + assert.Contains(t, string(content), "return bson.MarshalValue(e.String())") + assert.Contains(t, string(content), "func (e *Status) UnmarshalBSONValue(t bsontype.Type, data []byte) error") + + // test BSON marshaling with actual BSON package (without MongoDB) + type TestDoc struct { + Value string `bson:"value"` + } + + doc := TestDoc{Value: "active"} + data, err := bson.Marshal(doc) + require.NoError(t, err) + + var decoded TestDoc + err = bson.Unmarshal(data, &decoded) + require.NoError(t, err) + assert.Equal(t, "active", decoded.Value) +} + +// TestSQLIntegration tests SQL operations with SQLite +func TestSQLIntegration(t *testing.T) { + // generate enum first + testDir := t.TempDir() + setupTestEnum(t, testDir) + + // verify SQL code is generated + content, err := os.ReadFile(filepath.Join(testDir, "status_enum.go")) + require.NoError(t, err) + + assert.Contains(t, string(content), "func (e Status) Value() (driver.Value, error)") + assert.Contains(t, string(content), "func (e *Status) Scan(value interface{}) error") + + // test with actual SQLite + db, err := sql.Open("sqlite", ":memory:") + require.NoError(t, err) + defer db.Close() + + // create table + _, err = db.Exec(` + CREATE TABLE test_table ( + id INTEGER PRIMARY KEY, + status TEXT, + name TEXT + ) + `) + require.NoError(t, err) + + // test string storage and retrieval + testCases := []struct { + status string + name string + }{ + {"active", "Test Active"}, + {"inactive", "Test Inactive"}, + {"blocked", "Test Blocked"}, + {"unknown", "Test Unknown"}, + } + + for i, tc := range testCases { + _, err = db.Exec("INSERT INTO test_table (id, status, name) VALUES (?, ?, ?)", + i+1, tc.status, tc.name) + require.NoError(t, err) + + var status, name string + row := db.QueryRow("SELECT status, name FROM test_table WHERE id = ?", i+1) + err = row.Scan(&status, &name) + require.NoError(t, err) + + assert.Equal(t, tc.status, status) + assert.Equal(t, tc.name, name) + } + + // test NULL handling + _, err = db.Exec("INSERT INTO test_table (id, status, name) VALUES (?, NULL, ?)", + 999, "NULL Test") + require.NoError(t, err) + + var nullStatus sql.NullString + var name string + row := db.QueryRow("SELECT status, name FROM test_table WHERE id = ?", 999) + err = row.Scan(&nullStatus, &name) + require.NoError(t, err) + + assert.False(t, nullStatus.Valid) + assert.Equal(t, "NULL Test", name) +} + +// TestYAMLIntegration tests YAML marshaling +func TestYAMLIntegration(t *testing.T) { + // generate enum first + testDir := t.TempDir() + setupTestEnum(t, testDir) + + // verify YAML code is generated + content, err := os.ReadFile(filepath.Join(testDir, "status_enum.go")) + require.NoError(t, err) + + assert.Contains(t, string(content), "func (e Status) MarshalYAML() (any, error)") + assert.Contains(t, string(content), "func (e *Status) UnmarshalYAML(value *yaml.Node) error") + + // test YAML operations + type Config struct { + Name string `yaml:"name"` + Status string `yaml:"status"` + Count int `yaml:"count"` + } + + cfg := Config{ + Name: "test config", + Status: "active", + Count: 42, + } + + // marshal + data, err := yaml.Marshal(cfg) + require.NoError(t, err) + assert.Contains(t, string(data), "status: active") + + // unmarshal + var decoded Config + err = yaml.Unmarshal(data, &decoded) + require.NoError(t, err) + assert.Equal(t, cfg, decoded) + + // test error cases + invalidYAML := []byte("status: not_a_valid_status") + var badConfig Config + err = yaml.Unmarshal(invalidYAML, &badConfig) + // this should succeed as it's just a string field + require.NoError(t, err) + assert.Equal(t, "not_a_valid_status", badConfig.Status) +} + +// TestJSONIntegration tests JSON marshaling via TextMarshaler +func TestJSONIntegration(t *testing.T) { + // generate enum first + testDir := t.TempDir() + setupTestEnum(t, testDir) + + // verify TextMarshaler code is generated + content, err := os.ReadFile(filepath.Join(testDir, "status_enum.go")) + require.NoError(t, err) + + assert.Contains(t, string(content), "func (e Status) MarshalText() ([]byte, error)") + assert.Contains(t, string(content), "func (e *Status) UnmarshalText(text []byte) error") + + // test JSON operations + type Response struct { + Status string `json:"status"` + Count int `json:"count"` + Items []string `json:"items"` + } + + resp := Response{ + Status: "active", + Count: 3, + Items: []string{"a", "b", "c"}, + } + + // marshal + data, err := json.Marshal(resp) + require.NoError(t, err) + assert.Contains(t, string(data), `"status":"active"`) + + // unmarshal + var decoded Response + err = json.Unmarshal(data, &decoded) + require.NoError(t, err) + assert.Equal(t, resp, decoded) +} + +// TestEnumGeneration tests various enum generation scenarios +func TestEnumGeneration(t *testing.T) { + tests := []struct { + name string + enumDef string + typeName string + wantBSON bool + wantSQL bool + wantYAML bool + wantLowerCase bool + }{ + { + name: "basic enum no features", + enumDef: `package test +type color int +const ( + colorRed color = iota + colorGreen + colorBlue +)`, + typeName: "color", + }, + { + name: "enum with BSON only", + enumDef: `package test +type priority uint8 +const ( + priorityLow priority = iota + priorityMedium + priorityHigh +)`, + typeName: "priority", + wantBSON: true, + }, + { + name: "enum with SQL only", + enumDef: `package test +type state int32 +const ( + stateInit state = iota + stateRunning + stateStopped +)`, + typeName: "state", + wantSQL: true, + }, + { + name: "enum with lowercase names", + enumDef: `package test +type mode uint +const ( + modeRead mode = iota + modeWrite + modeExecute +)`, + typeName: "mode", + wantLowerCase: true, + }, + { + name: "enum with all features", + enumDef: `package test +type level int +const ( + levelDebug level = iota + levelInfo + levelWarn + levelError +)`, + typeName: "level", + wantBSON: true, + wantSQL: true, + wantYAML: true, + wantLowerCase: true, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + testDir := t.TempDir() + + // write enum definition + enumFile := filepath.Join(testDir, "enum.go") + err := os.WriteFile(enumFile, []byte(tt.enumDef), 0o644) + require.NoError(t, err) + + // generate enum + gen, err := New(tt.typeName, testDir) + require.NoError(t, err) + + if tt.wantBSON { + gen.SetGenerateBSON(true) + } + if tt.wantSQL { + gen.SetGenerateSQL(true) + } + if tt.wantYAML { + gen.SetGenerateYAML(true) + } + if tt.wantLowerCase { + gen.SetLowerCase(true) + } + + err = gen.Parse(testDir) + require.NoError(t, err) + + err = gen.Generate() + require.NoError(t, err) + + // verify generated file + generatedFile := filepath.Join(testDir, tt.typeName+"_enum.go") + require.FileExists(t, generatedFile) + + content, err := os.ReadFile(generatedFile) + require.NoError(t, err) + + // check expected features + if tt.wantBSON { + assert.Contains(t, string(content), "MarshalBSONValue") + assert.Contains(t, string(content), "UnmarshalBSONValue") + } else { + assert.NotContains(t, string(content), "MarshalBSONValue") + } + + if tt.wantSQL { + assert.Contains(t, string(content), "Value() (driver.Value") + assert.Contains(t, string(content), "Scan(value interface{})") + } else { + assert.NotContains(t, string(content), "driver.Value") + } + + if tt.wantYAML { + assert.Contains(t, string(content), "MarshalYAML") + assert.Contains(t, string(content), "UnmarshalYAML") + } else { + assert.NotContains(t, string(content), "MarshalYAML") + } + }) + } +} + +// TestRuntimeIntegration tests the full pipeline: build binary → generate enums → test with real databases +func TestRuntimeIntegration(t *testing.T) { + if testing.Short() { + t.Skip("skipping integration test in short mode") + } + + // skip in CI due to timeout issues with downloading dependencies + if os.Getenv("CI") != "" { + t.Skip("skipping runtime integration test in CI") + } + + // 1. Build the enum binary + binPath := filepath.Join(t.TempDir(), "enum") + cmd := exec.Command("go", "build", "-o", binPath, "github.com/go-pkgz/enum") + output, err := cmd.CombinedOutput() + require.NoError(t, err, "failed to build enum binary: %s", output) + + // 2. Create a temp package for testing + pkgDir := t.TempDir() + + // Copy enum definitions from testdata + statusSrc, err := os.ReadFile("testdata/integration/status.go") + require.NoError(t, err) + err = os.WriteFile(filepath.Join(pkgDir, "status.go"), statusSrc, 0o644) + require.NoError(t, err) + + prioritySrc, err := os.ReadFile("testdata/integration/priority.go") + require.NoError(t, err) + err = os.WriteFile(filepath.Join(pkgDir, "priority.go"), prioritySrc, 0o644) + require.NoError(t, err) + + // 3. Generate enums using the built binary + // Generate status enum + cmd = exec.Command(binPath, "-type=status", "-lower", "-sql", "-bson", "-yaml") + cmd.Dir = pkgDir + output, err = cmd.CombinedOutput() + require.NoError(t, err, "failed to generate status enum: %s", output) + + // Generate priority enum + cmd = exec.Command(binPath, "-type=priority", "-sql", "-bson", "-yaml") + cmd.Dir = pkgDir + output, err = cmd.CombinedOutput() + require.NoError(t, err, "failed to generate priority enum: %s", output) + + // Verify generated files exist + require.FileExists(t, filepath.Join(pkgDir, "status_enum.go")) + require.FileExists(t, filepath.Join(pkgDir, "priority_enum.go")) + + // 4. Create go.mod for the test package + goModContent := []byte(`module testpkg + +go 1.24 + +require ( + github.com/stretchr/testify v1.10.0 + go.mongodb.org/mongo-driver v1.17.4 + gopkg.in/yaml.v3 v3.0.1 + modernc.org/sqlite v1.38.2 + github.com/go-pkgz/testutils v0.4.3 +) +`) + err = os.WriteFile(filepath.Join(pkgDir, "go.mod"), goModContent, 0o644) + require.NoError(t, err) + + // 5. Copy test file from testdata + testContent, err := os.ReadFile("testdata/integration/enum_test.go") + require.NoError(t, err) + err = os.WriteFile(filepath.Join(pkgDir, "enum_test.go"), testContent, 0o644) + require.NoError(t, err) + + // 6. Run go mod tidy + cmd = exec.Command("go", "mod", "tidy") + cmd.Dir = pkgDir + output, err = cmd.CombinedOutput() + if err != nil { + // it's ok if mod tidy fails due to network, as long as test runs + t.Logf("go mod tidy output: %s", output) + } + + // 7. Run the tests in the generated package + cmd = exec.Command("go", "test", "-v", ".") + cmd.Dir = pkgDir + output, err = cmd.CombinedOutput() + + t.Logf("Test output:\n%s", output) + + if err != nil { + t.Fatalf("Generated enum tests failed: %v", err) + } + + // verify expected tests ran + outputStr := string(output) + require.Contains(t, outputStr, "PASS") + require.Contains(t, outputStr, "TestGeneratedEnumWithMongoDB") + require.Contains(t, outputStr, "TestGeneratedEnumWithSQL") + require.Contains(t, outputStr, "TestGeneratedEnumWithYAML") + require.Contains(t, outputStr, "TestGeneratedEnumWithJSON") +} + +// TestRuntimeIntegrationErrors tests error cases in the generation pipeline +func TestRuntimeIntegrationErrors(t *testing.T) { + if testing.Short() { + t.Skip("skipping integration test in short mode") + } + + // build the enum binary + binPath := filepath.Join(t.TempDir(), "enum") + cmd := exec.Command("go", "build", "-o", binPath, "github.com/go-pkgz/enum") + output, err := cmd.CombinedOutput() + require.NoError(t, err, "failed to build enum binary: %s", output) + + t.Run("missing type", func(t *testing.T) { + pkgDir := t.TempDir() + + // create enum file + writeErr := os.WriteFile(filepath.Join(pkgDir, "test.go"), []byte(`package test +type myenum int +const ( + myenumOne myenum = iota +)`), 0o644) + require.NoError(t, writeErr) + + // try to generate non-existent type + missingCmd := exec.Command(binPath, "-type=missing") + missingCmd.Dir = pkgDir + missingOutput, missingErr := missingCmd.CombinedOutput() + require.Error(t, missingErr) + require.Contains(t, string(missingOutput), "type missing") + }) + + t.Run("no constants", func(t *testing.T) { + pkgDir := t.TempDir() + + // create enum without constants + writeErr := os.WriteFile(filepath.Join(pkgDir, "test.go"), []byte(`package test +type empty int +`), 0o644) + require.NoError(t, writeErr) + + // try to generate + emptyCmd := exec.Command(binPath, "-type=empty") + emptyCmd.Dir = pkgDir + emptyOutput, emptyErr := emptyCmd.CombinedOutput() + require.Error(t, emptyErr) + require.Contains(t, string(emptyOutput), "no const values found") + }) + + t.Run("invalid type name", func(t *testing.T) { + pkgDir := t.TempDir() + + // uppercase type name should fail + invalidCmd := exec.Command(binPath, "-type=Status") + invalidCmd.Dir = pkgDir + invalidOutput, invalidErr := invalidCmd.CombinedOutput() + require.Error(t, invalidErr) + require.Contains(t, string(invalidOutput), "first letter must be lowercase") + }) +} + +// TestErrorHandling tests various error conditions +func TestErrorHandling(t *testing.T) { + t.Run("invalid enum type", func(t *testing.T) { + testDir := t.TempDir() + + // write file without the requested type + err := os.WriteFile(filepath.Join(testDir, "test.go"), []byte(`package test +type other int +const ( + otherOne other = iota +)`), 0o644) + require.NoError(t, err) + + gen, err := New("missing", testDir) + require.NoError(t, err) + + err = gen.Parse(testDir) + require.Error(t, err) + assert.Contains(t, err.Error(), "type missing") + }) + + t.Run("no constants found", func(t *testing.T) { + testDir := t.TempDir() + + // write type without constants + err := os.WriteFile(filepath.Join(testDir, "test.go"), []byte(`package test +type empty int +`), 0o644) + require.NoError(t, err) + + gen, err := New("empty", testDir) + require.NoError(t, err) + + err = gen.Parse(testDir) + require.Error(t, err) + assert.Contains(t, err.Error(), "no const values found") + }) +} + +// setupTestEnum creates a test enum in the given directory +func setupTestEnum(t *testing.T, dir string) { + t.Helper() + + enumFile := filepath.Join(dir, "status.go") + err := os.WriteFile(enumFile, []byte(`package test + +type status uint8 + +const ( + statusUnknown status = iota + statusActive + statusInactive + statusBlocked + statusDeleted +) +`), 0o644) + require.NoError(t, err) + + gen, err := New("status", dir) + require.NoError(t, err) + gen.SetLowerCase(true) + gen.SetGenerateBSON(true) + gen.SetGenerateSQL(true) + gen.SetGenerateYAML(true) + + err = gen.Parse(dir) + require.NoError(t, err) + + err = gen.Generate() + require.NoError(t, err) +} + +// NullableEnum wraps an enum for SQL NULL support +type NullableEnum struct { + Enum interface{ driver.Valuer } + Valid bool +} + +// Value implements driver.Valuer +func (n NullableEnum) Value() (driver.Value, error) { + if !n.Valid { + return nil, nil + } + return n.Enum.(driver.Valuer).Value() +} + +// Scan implements sql.Scanner +func (n *NullableEnum) Scan(value interface{}) error { + if value == nil { + n.Valid = false + return nil + } + n.Valid = true + // actual scanning would be delegated to the enum's Scan method + return nil +} diff --git a/internal/generator/testdata/integration/enum_test.go b/internal/generator/testdata/integration/enum_test.go new file mode 100644 index 0000000..9edf42b --- /dev/null +++ b/internal/generator/testdata/integration/enum_test.go @@ -0,0 +1,169 @@ +package integration + +import ( + "context" + "database/sql" + "encoding/json" + "testing" + + "github.com/go-pkgz/testutils/containers" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + "go.mongodb.org/mongo-driver/bson" + "gopkg.in/yaml.v3" + _ "modernc.org/sqlite" +) + +func TestGeneratedEnumWithMongoDB(t *testing.T) { + ctx := context.Background() + + // start MongoDB container + mongoContainer := containers.NewMongoTestContainer(ctx, t, 7) + defer mongoContainer.Close(ctx) + + // get collection + coll := mongoContainer.Collection("test_db") + + // test with generated Status enum + type Doc struct { + ID string `bson:"_id"` + Status Status `bson:"status"` + Name string `bson:"name"` + } + + // insert with enum value + doc := Doc{ + ID: "test1", + Status: StatusActive, + Name: "Test Document", + } + + _, err := coll.InsertOne(ctx, doc) + require.NoError(t, err, "should insert document with enum") + + // retrieve and verify + var retrieved Doc + err = coll.FindOne(ctx, bson.M{"_id": "test1"}).Decode(&retrieved) + require.NoError(t, err, "should retrieve document") + + assert.Equal(t, StatusActive, retrieved.Status) + assert.Equal(t, "active", retrieved.Status.String()) + + // verify BSON storage format is string + var raw bson.M + err = coll.FindOne(ctx, bson.M{"_id": "test1"}).Decode(&raw) + require.NoError(t, err) + + statusField, ok := raw["status"].(string) + assert.True(t, ok, "status should be stored as string in BSON") + assert.Equal(t, "active", statusField) +} + +func TestGeneratedEnumWithSQL(t *testing.T) { + // create in-memory SQLite database + db, err := sql.Open("sqlite", ":memory:") + require.NoError(t, err) + defer db.Close() + + // create table + _, err = db.Exec(` + CREATE TABLE records ( + id INTEGER PRIMARY KEY, + status TEXT, + priority INTEGER + ) + `) + require.NoError(t, err) + + // test Status enum + _, err = db.Exec("INSERT INTO records (id, status, priority) VALUES (?, ?, ?)", + 1, StatusActive, PriorityHigh) + require.NoError(t, err) + + var status Status + var priority Priority + row := db.QueryRow("SELECT status, priority FROM records WHERE id = ?", 1) + err = row.Scan(&status, &priority) + require.NoError(t, err) + + assert.Equal(t, StatusActive, status) + assert.Equal(t, "active", status.String()) + assert.Equal(t, PriorityHigh, priority) + + // test NULL handling + _, err = db.Exec("INSERT INTO records (id, status, priority) VALUES (?, NULL, NULL)", 2) + require.NoError(t, err) + + row = db.QueryRow("SELECT status, priority FROM records WHERE id = ?", 2) + err = row.Scan(&status, &priority) + require.NoError(t, err) + assert.Equal(t, StatusUnknown, status) // zero value + assert.Equal(t, PriorityLow, priority) // zero value for priority (0) +} + +func TestGeneratedEnumWithYAML(t *testing.T) { + // test Status enum + status := StatusInactive + data, err := yaml.Marshal(status) + require.NoError(t, err) + assert.Equal(t, "inactive\n", string(data)) + + var unmarshaled Status + err = yaml.Unmarshal(data, &unmarshaled) + require.NoError(t, err) + assert.Equal(t, StatusInactive, unmarshaled) + + // test in struct + type Config struct { + Status Status `yaml:"status"` + Priority Priority `yaml:"priority"` + } + + cfg := Config{ + Status: StatusBlocked, + Priority: PriorityCritical, + } + + data, err = yaml.Marshal(cfg) + require.NoError(t, err) + + var decoded Config + err = yaml.Unmarshal(data, &decoded) + require.NoError(t, err) + assert.Equal(t, cfg, decoded) + + // test error handling + var badStatus Status + err = yaml.Unmarshal([]byte("invalid_status"), &badStatus) + assert.Error(t, err) + assert.Contains(t, err.Error(), "invalid status") +} + +func TestGeneratedEnumWithJSON(t *testing.T) { + // test Status enum + status := StatusPending + data, err := json.Marshal(status) + require.NoError(t, err) + assert.Equal(t, `"pending"`, string(data)) + + var unmarshaled Status + err = json.Unmarshal(data, &unmarshaled) + require.NoError(t, err) + assert.Equal(t, StatusPending, unmarshaled) + + // test Priority enum + priority := PriorityMedium + data, err = json.Marshal(priority) + require.NoError(t, err) + + var unmarshaledPriority Priority + err = json.Unmarshal(data, &unmarshaledPriority) + require.NoError(t, err) + assert.Equal(t, PriorityMedium, unmarshaledPriority) + + // test error handling + var badStatus Status + err = json.Unmarshal([]byte(`"not_a_status"`), &badStatus) + assert.Error(t, err) + assert.Contains(t, err.Error(), "invalid status") +} diff --git a/internal/generator/testdata/integration/priority.go b/internal/generator/testdata/integration/priority.go new file mode 100644 index 0000000..c6c58c1 --- /dev/null +++ b/internal/generator/testdata/integration/priority.go @@ -0,0 +1,13 @@ +package integration + +//go:generate ../../../../enum -type=priority -sql -bson -yaml + +type priority int32 + +const ( + priorityNone priority = -1 + priorityLow priority = 0 + priorityMedium priority = 100 + priorityHigh priority = 1000 + priorityCritical priority = 999999 +) diff --git a/internal/generator/testdata/integration/priority_enum.go b/internal/generator/testdata/integration/priority_enum.go new file mode 100644 index 0000000..fba2afa --- /dev/null +++ b/internal/generator/testdata/integration/priority_enum.go @@ -0,0 +1,197 @@ +// Code generated by enum generator; DO NOT EDIT. +package integration + +import ( + "database/sql/driver" + "fmt" + "go.mongodb.org/mongo-driver/bson" + "go.mongodb.org/mongo-driver/bson/bsontype" + "gopkg.in/yaml.v3" + "strings" +) + +// Priority is the exported type for the enum +type Priority struct { + name string + value int32 +} + +func (e Priority) String() string { return e.name } + +// Index returns the underlying integer value +func (e Priority) Index() int32 { return e.value } + +// MarshalText implements encoding.TextMarshaler +func (e Priority) MarshalText() ([]byte, error) { + return []byte(e.name), nil +} + +// UnmarshalText implements encoding.TextUnmarshaler +func (e *Priority) UnmarshalText(text []byte) error { + var err error + *e, err = ParsePriority(string(text)) + return err +} + +// Value implements the driver.Valuer interface +func (e Priority) Value() (driver.Value, error) { + return e.name, nil +} + +// Scan implements the sql.Scanner interface +func (e *Priority) Scan(value interface{}) error { + if value == nil { + // try to find zero value + for _, v := range PriorityValues { + if v.Index() == 0 { + *e = v + return nil + } + } + // no zero value found, return error + return fmt.Errorf("cannot scan nil into Priority: no zero value defined") + } + + str, ok := value.(string) + if !ok { + if b, ok := value.([]byte); ok { + str = string(b) + } else { + return fmt.Errorf("invalid priority value: %v", value) + } + } + + val, err := ParsePriority(str) + if err != nil { + return err + } + + *e = val + return nil +} + +// MarshalBSONValue implements bson.ValueMarshaler and encodes the enum as a string +func (e Priority) MarshalBSONValue() (bsontype.Type, []byte, error) { + return bson.MarshalValue(e.String()) +} + +// UnmarshalBSONValue implements bson.ValueUnmarshaler and decodes the enum from a string +func (e *Priority) UnmarshalBSONValue(t bsontype.Type, data []byte) error { + var s string + if err := bson.UnmarshalValue(t, data, &s); err != nil { + return err + } + val, err := ParsePriority(s) + if err != nil { + return err + } + *e = val + return nil +} + +// MarshalYAML implements yaml.Marshaler and encodes the enum as a string +func (e Priority) MarshalYAML() (any, error) { + return e.String(), nil +} + +// UnmarshalYAML implements yaml.Unmarshaler and decodes the enum from a string scalar +func (e *Priority) UnmarshalYAML(value *yaml.Node) error { + if value == nil || value.Kind != yaml.ScalarNode { + return fmt.Errorf("invalid YAML for priority: expected scalar string") + } + val, err := ParsePriority(value.Value) + if err != nil { + return err + } + *e = val + return nil +} + +// _priorityParseMap is used for efficient string to enum conversion +var _priorityParseMap = map[string]Priority{ + "none": PriorityNone, + "low": PriorityLow, + "medium": PriorityMedium, + "high": PriorityHigh, + "critical": PriorityCritical, +} + +// ParsePriority converts string to priority enum value +func ParsePriority(v string) (Priority, error) { + + if val, ok := _priorityParseMap[strings.ToLower(v)]; ok { + return val, nil + } + + return Priority{}, fmt.Errorf("invalid priority: %s", v) +} + +// MustPriority is like ParsePriority but panics if string is invalid +func MustPriority(v string) Priority { + r, err := ParsePriority(v) + if err != nil { + panic(err) + } + return r +} + +// Public constants for priority values +var ( + PriorityNone = Priority{name: "None", value: -1} + PriorityLow = Priority{name: "Low", value: 0} + PriorityMedium = Priority{name: "Medium", value: 100} + PriorityHigh = Priority{name: "High", value: 1000} + PriorityCritical = Priority{name: "Critical", value: 999999} +) + +// PriorityValues contains all possible enum values +var PriorityValues = []Priority{ + PriorityNone, + PriorityLow, + PriorityMedium, + PriorityHigh, + PriorityCritical, +} + +// PriorityNames contains all possible enum names +var PriorityNames = []string{ + "None", + "Low", + "Medium", + "High", + "Critical", +} + +// PriorityIter returns a function compatible with Go 1.23's range-over-func syntax. +// It yields all Priority values in declaration order. Example: +// +// for v := range PriorityIter() { +// // use v +// } +func PriorityIter() func(yield func(Priority) bool) { + return func(yield func(Priority) bool) { + for _, v := range PriorityValues { + if !yield(v) { + break + } + } + } +} + +// These variables are used to prevent the compiler from reporting unused errors +// for the original enum constants. They are intentionally placed in a var block +// that is compiled away by the Go compiler. +var _ = func() bool { + var _ priority = priority(0) + // This avoids "defined but not used" linter error for priorityNone + var _ priority = priorityNone + // This avoids "defined but not used" linter error for priorityLow + var _ priority = priorityLow + // This avoids "defined but not used" linter error for priorityMedium + var _ priority = priorityMedium + // This avoids "defined but not used" linter error for priorityHigh + var _ priority = priorityHigh + // This avoids "defined but not used" linter error for priorityCritical + var _ priority = priorityCritical + return true +}() diff --git a/internal/generator/testdata/integration/status.go b/internal/generator/testdata/integration/status.go new file mode 100644 index 0000000..bb03283 --- /dev/null +++ b/internal/generator/testdata/integration/status.go @@ -0,0 +1,15 @@ +package integration + +//go:generate ../../../../enum -type=status -lower -sql -bson -yaml + +type status uint8 + +const ( + statusUnknown status = iota + statusActive + statusInactive + statusBlocked + statusDeleted + statusPending + statusArchived +) diff --git a/internal/generator/testdata/integration/status_enum.go b/internal/generator/testdata/integration/status_enum.go new file mode 100644 index 0000000..71577d7 --- /dev/null +++ b/internal/generator/testdata/integration/status_enum.go @@ -0,0 +1,208 @@ +// Code generated by enum generator; DO NOT EDIT. +package integration + +import ( + "database/sql/driver" + "fmt" + "go.mongodb.org/mongo-driver/bson" + "go.mongodb.org/mongo-driver/bson/bsontype" + "gopkg.in/yaml.v3" +) + +// Status is the exported type for the enum +type Status struct { + name string + value uint8 +} + +func (e Status) String() string { return e.name } + +// Index returns the underlying integer value +func (e Status) Index() uint8 { return e.value } + +// MarshalText implements encoding.TextMarshaler +func (e Status) MarshalText() ([]byte, error) { + return []byte(e.name), nil +} + +// UnmarshalText implements encoding.TextUnmarshaler +func (e *Status) UnmarshalText(text []byte) error { + var err error + *e, err = ParseStatus(string(text)) + return err +} + +// Value implements the driver.Valuer interface +func (e Status) Value() (driver.Value, error) { + return e.name, nil +} + +// Scan implements the sql.Scanner interface +func (e *Status) Scan(value interface{}) error { + if value == nil { + // try to find zero value + for _, v := range StatusValues { + if v.Index() == 0 { + *e = v + return nil + } + } + // no zero value found, return error + return fmt.Errorf("cannot scan nil into Status: no zero value defined") + } + + str, ok := value.(string) + if !ok { + if b, ok := value.([]byte); ok { + str = string(b) + } else { + return fmt.Errorf("invalid status value: %v", value) + } + } + + val, err := ParseStatus(str) + if err != nil { + return err + } + + *e = val + return nil +} + +// MarshalBSONValue implements bson.ValueMarshaler and encodes the enum as a string +func (e Status) MarshalBSONValue() (bsontype.Type, []byte, error) { + return bson.MarshalValue(e.String()) +} + +// UnmarshalBSONValue implements bson.ValueUnmarshaler and decodes the enum from a string +func (e *Status) UnmarshalBSONValue(t bsontype.Type, data []byte) error { + var s string + if err := bson.UnmarshalValue(t, data, &s); err != nil { + return err + } + val, err := ParseStatus(s) + if err != nil { + return err + } + *e = val + return nil +} + +// MarshalYAML implements yaml.Marshaler and encodes the enum as a string +func (e Status) MarshalYAML() (any, error) { + return e.String(), nil +} + +// UnmarshalYAML implements yaml.Unmarshaler and decodes the enum from a string scalar +func (e *Status) UnmarshalYAML(value *yaml.Node) error { + if value == nil || value.Kind != yaml.ScalarNode { + return fmt.Errorf("invalid YAML for status: expected scalar string") + } + val, err := ParseStatus(value.Value) + if err != nil { + return err + } + *e = val + return nil +} + +// _statusParseMap is used for efficient string to enum conversion +var _statusParseMap = map[string]Status{ + "unknown": StatusUnknown, + "active": StatusActive, + "inactive": StatusInactive, + "blocked": StatusBlocked, + "deleted": StatusDeleted, + "pending": StatusPending, + "archived": StatusArchived, +} + +// ParseStatus converts string to status enum value +func ParseStatus(v string) (Status, error) { + + if val, ok := _statusParseMap[v]; ok { + return val, nil + } + + return Status{}, fmt.Errorf("invalid status: %s", v) +} + +// MustStatus is like ParseStatus but panics if string is invalid +func MustStatus(v string) Status { + r, err := ParseStatus(v) + if err != nil { + panic(err) + } + return r +} + +// Public constants for status values +var ( + StatusUnknown = Status{name: "unknown", value: 0} + StatusActive = Status{name: "active", value: 1} + StatusInactive = Status{name: "inactive", value: 2} + StatusBlocked = Status{name: "blocked", value: 3} + StatusDeleted = Status{name: "deleted", value: 4} + StatusPending = Status{name: "pending", value: 5} + StatusArchived = Status{name: "archived", value: 6} +) + +// StatusValues contains all possible enum values +var StatusValues = []Status{ + StatusUnknown, + StatusActive, + StatusInactive, + StatusBlocked, + StatusDeleted, + StatusPending, + StatusArchived, +} + +// StatusNames contains all possible enum names +var StatusNames = []string{ + "unknown", + "active", + "inactive", + "blocked", + "deleted", + "pending", + "archived", +} + +// StatusIter returns a function compatible with Go 1.23's range-over-func syntax. +// It yields all Status values in declaration order. Example: +// +// for v := range StatusIter() { +// // use v +// } +func StatusIter() func(yield func(Status) bool) { + return func(yield func(Status) bool) { + for _, v := range StatusValues { + if !yield(v) { + break + } + } + } +} + +// These variables are used to prevent the compiler from reporting unused errors +// for the original enum constants. They are intentionally placed in a var block +// that is compiled away by the Go compiler. +var _ = func() bool { + var _ status = status(0) + // This avoids "defined but not used" linter error for statusUnknown + var _ status = statusUnknown + // This avoids "defined but not used" linter error for statusActive + var _ status = statusActive + // This avoids "defined but not used" linter error for statusInactive + var _ status = statusInactive + // This avoids "defined but not used" linter error for statusBlocked + var _ status = statusBlocked + // This avoids "defined but not used" linter error for statusDeleted + var _ status = statusDeleted + // This avoids "defined but not used" linter error for statusPending + var _ status = statusPending + // This avoids "defined but not used" linter error for statusArchived + var _ status = statusArchived + return true +}() diff --git a/main.go b/main.go index dadc4ec..eb4b212 100644 --- a/main.go +++ b/main.go @@ -18,6 +18,10 @@ func main() { pathFlag := flag.String("path", "", "output directory path (default: same as source)") lowerFlag := flag.Bool("lower", false, "use lowercase for string representation (e.g., 'active' instead of 'Active')") getterFlag := flag.Bool("getter", false, "generate GetByID function to retrieve enum by integer value (requires unique IDs)") + // optional integrations (all disabled by default to avoid extra deps) + sqlFlag := flag.Bool("sql", false, "generate SQL support (database/sql/driver.Valuer and sql.Scanner)") + bsonFlag := flag.Bool("bson", false, "generate MongoDB BSON support (MarshalBSONValue/UnmarshalBSONValue)") + yamlFlag := flag.Bool("yaml", false, "generate YAML support (gopkg.in/yaml.v3 Marshaler/Unmarshaler)") helpFlag := flag.Bool("help", false, "show usage") versionFlag := flag.Bool("version", false, "print version") flag.Parse() @@ -51,6 +55,9 @@ func main() { gen.SetLowerCase(*lowerFlag) gen.SetGenerateGetter(*getterFlag) + gen.SetGenerateSQL(*sqlFlag) + gen.SetGenerateBSON(*bsonFlag) + gen.SetGenerateYAML(*yamlFlag) if err := gen.Parse("."); err != nil { fmt.Printf("%v\n", err)