Eugene is a CLI tool for generating Go code from OpenAPI specifications (3.0, 3.1, 3.2). It generates type-safe clients, servers, and type definitions from your API specs.
This project is heavily inspired by oapi-codegen/oapi-codegen and is an attempt to bring newest OpenAPI specifications while offering similar code generation experience.
Underneath, the tool is built upon pb33f/libopenapi that made creating this tool possible.
go install github.com/kolah/eugene/v2/cmd/eugene@latestor use it as a tool dependency:
go get -tool github.com/kolah/eugene/v2/cmd/eugene@latest- Create a configuration file
eugene.yaml:
spec: ./api/openapi.yaml
go:
package: api
output-dir: ./gen
targets:
- types
- server
- client- Run the generator:
eugene generate goOr generate specific targets:
eugene generate go types
eugene generate go server
eugene generate go alleugene generate go [target] [flags]
Targets:
types Generate Go type definitions
server Generate Go server code
strict-server Generate Go strict server with typed responses
client Generate Go HTTP client
spec Generate embedded OpenAPI spec
all Generate all targets
Common Flags:
-c, --config string Config file (default: eugene.yaml)
-s, --spec string OpenAPI spec path
--templates string Custom templates directory
--exclude-schemas strings Schemas to exclude
--include-tags strings Tags to include (exclusive)
--exclude-tags strings Tags to exclude
--dry-run Print output without writing files
Go Flags:
-o, --output-dir string Output directory
-p, --package string Go package name
-f, --server-framework string Server framework: echo, chi, stdlib
--enum-strategy string Enum strategy: const, type, struct
--uuid-package string UUID type: string, google, gofrs
--nullable-strategy string Nullable strategy: pointer, nullable
--allof-strategy string AllOf strategy: embed, flatten
--enable-yaml-tags Generate yaml tags alongside json tags
--additional-initialisms Custom initialisms for naming (e.g., GTIN,SKU)
Eugene supports configuration via YAML file, CLI flags, and environment variables. Loading order: defaults -> YAML file -> CLI flags.
spec: api/openapi.yaml
templates:
dir: ./custom-templates
exclude-schemas:
- InternalType
include-tags:
- public
go:
package: api
output-dir: ./gen
server-framework: echo
targets:
- types
- server
- strict-server
- client
- spec
types:
enum-strategy: const # const, type, or struct
uuid-package: google # string, google, or gofrs
nullable-strategy: pointer # pointer or nullable
allof-strategy: embed # embed or flatten
output-options:
enable-yaml-tags: true
additional-initialisms:
- GTIN
- SKU
import-mapping:
"#/components/schemas/Error": github.com/myorg/api/commonGenerates Go structs from OpenAPI schemas:
type Pet struct {
ID int64 `json:"id"`
Name string `json:"name"`
Tag string `json:"tag,omitempty"`
}Generates a server interface and registration function:
type ServerInterface interface {
ListPets(ctx echo.Context) error
CreatePet(ctx echo.Context) error
GetPet(ctx echo.Context, id int64) error
}
func RegisterHandlers(e *echo.Echo, si ServerInterface) {
// ...
}Type-safe server with parsed request/response objects:
type StrictServerInterface interface {
GetPet(ctx context.Context, request GetPetRequest) (GetPetResponse, error)
}
type GetPetRequest struct {
ID int64
}
type GetPetResponse interface {
VisitGetPetResponse(w http.ResponseWriter) error
}HTTP client with typed methods:
type Client struct {
baseURL string
httpClient *http.Client
}
func (c *Client) GetPet(ctx context.Context, id int64) (*Pet, error) {
// ...
}Eugene supports three server frameworks:
| Framework | Flag | Import |
|---|---|---|
| Echo | echo |
github.com/labstack/echo/v4 |
| Chi | chi |
github.com/go-chi/chi/v5 |
| stdlib | stdlib |
net/http |
Eugene supports custom extensions for fine-grained control:
| Extension | Purpose | Example |
|---|---|---|
x-oink-go-type |
Override Go type | x-oink-go-type: time.Duration |
x-oink-go-type-import |
Import path | x-oink-go-type-import: {path: "time"} |
x-oink-go-name |
Override field/type name | x-oink-go-name: CustomerID |
x-oink-extra-tags |
Add struct tags | x-oink-extra-tags: {validate: "required"} |
x-oink-omitempty |
Force omitempty | x-oink-omitempty: true |
x-oink-omitzero |
Force omitzero | x-oink-omitzero: true |
x-oink-json-ignore |
Exclude from JSON | x-oink-json-ignore: true |
components:
schemas:
User:
type: object
properties:
id:
type: string
x-oink-go-type: uuid.UUID
x-oink-go-type-import:
path: github.com/google/uuid
email:
type: string
x-oink-extra-tags:
validate: required,email
db: emailtype PetStatus string
const (
PetStatusAvailable PetStatus = "available"
PetStatusPending PetStatus = "pending"
)type PetStatus string
const (
PetStatusAvailable PetStatus = "available"
PetStatusPending PetStatus = "pending"
)type PetStatus struct {
value string
}
func (e PetStatus) String() string { return e.value }
func (e PetStatus) IsValid() bool { ... }
var (
PetStatusAvailable = PetStatus{value: "available"}
PetStatusPending = PetStatus{value: "pending"}
)Uses Go struct embedding for allOf compositions when all components are references:
type Employee struct {
Person // embedded
EmployeeID string `json:"employee_id"`
}Merges all properties from allOf schemas into a single flat struct:
type Employee struct {
Name string `json:"name"` // from Person
Email string `json:"email"` // from Person
EmployeeID string `json:"employee_id"`
}Eugene generates union types with discriminator support:
type PaymentSource struct {
Type string `json:"-"`
Raw json.RawMessage `json:"-"`
}
func (s *PaymentSource) AsCard() (*Card, error) { ... }
func (s *PaymentSource) AsBankAccount() (*BankAccount, error) { ... }A $ref variant's accessor is named after the schema. An inline variant's accessor is named after its Go type: string gives AsString(), []string gives AsStringList(), map[string]int gives AsIntMap() and time.Time gives AsTime(). An inline object generates a type and uses its name, such as AsPaymentSourceVariant(). Inline variants with the same Go type share one accessor, so string and format: date together give a single AsString(). If an inline variant's name clashes with a different type in the union, the inline one takes a numeric suffix (AsString2()) and generation prints a warning.
Set x-oink-go-name on an inline variant to choose its accessor name. It also keeps the name stable if variants are reordered, and two variants can use it to keep separate accessors for the same Go type. Using a name already taken in the union is a generation error.
Schedule:
oneOf:
- $ref: "#/components/schemas/Time" # AsTime()
- type: string
format: date-time
x-oink-go-name: StartsAt # AsStartsAt() returns *time.TimeBoth client and server support Server-Sent Events:
Client:
stream, err := client.StreamEvents(ctx)
defer stream.Close()
for stream.Next() {
event := stream.Current()
var data MyEvent
event.Decode(&data)
}Server:
func (s *Server) StreamEvents(w http.ResponseWriter, r *http.Request) {
sse, _ := NewSSEWriter(w)
sse.WriteEvent("message", myData)
}Override built-in templates by providing a custom templates directory:
templates:
dir: ./my-templatesTemplates use Go's text/template with custom functions:
pascalCase,camelCase,snakeCase- naming conventionsgoType- OpenAPI schema to Go typegoComment- format as Go commentisRequired,isNullable- schema helpers
eugene/
βββ cmd/main.go # CLI entry point
βββ internal/
β βββ cli/ # Cobra commands
β βββ config/ # Configuration
β βββ loader/ # OpenAPI parsing (libopenapi)
β βββ model/ # Internal representation
β βββ codegen/ # Generation pipeline
β βββ golang/ # Go-specific logic
β βββ templates/ # Template engine
β βββ targets/ # Generation targets
βββ templates/ # Embedded templates
βββ testdata/ # Test fixtures
| Purpose | Library |
|---|---|
| OpenAPI Parsing | pb33f/libopenapi |
| CLI | spf13/cobra |
| Config | knadh/koanf |
| Formatting | golang.org/x/tools/imports |
Integration tests live in a separate tests/ module to avoid polluting the main module with framework-specific dependencies (Echo, Chi).
cd tests
go test ./...MIT