Programmatic API¶
This guide shows how to use oapi-codegen as a library in your Go code, rather than as a command-line tool.
Overview¶
The oapi-codegen package provides a programmatic API for generating Go code from OpenAPI specifications. This is useful when you need to:
- Integrate code generation into your build pipeline
- Generate code dynamically at runtime
- Access type definitions and operations programmatically
- Build custom tooling on top of oapi-codegen
Quick Start¶
Simple Code Generation¶
The simplest way to generate code is using the codegen.Generate() function:
package main
import (
"fmt"
"os"
"github.com/doordash-oss/oapi-codegen-dd/v3/pkg/codegen"
)
func main() {
// Read OpenAPI spec
specContents, err := os.ReadFile("api.yaml")
if err != nil {
panic(err)
}
// Create configuration with defaults
cfg := codegen.NewDefaultConfiguration()
cfg.PackageName = "api"
// Generate code
generatedCode, err := codegen.Generate(specContents, cfg)
if err != nil {
panic(err)
}
// Write to file
if err := os.WriteFile("generated.go", []byte(generatedCode["all"]), 0644); err != nil {
panic(err)
}
fmt.Println("Code generated successfully to generated.go")
}
Advanced: Two-Step Generation¶
For more control, use the two-step approach with CreateParseContext() and NewParser():
package main
import (
"fmt"
"os"
"github.com/doordash-oss/oapi-codegen-dd/v3/pkg/codegen"
)
func main() {
// Read OpenAPI spec
specContents, err := os.ReadFile("api.yaml")
if err != nil {
panic(err)
}
// Create configuration
cfg := codegen.NewDefaultConfiguration()
// Step 1: Create parse context
parseCtx, errs := codegen.CreateParseContext(specContents, cfg)
if len(errs) > 0 {
panic(fmt.Sprintf("parsing spec: %v", errs[0]))
}
// Inspect what was parsed
fmt.Printf("Found %d operations\n", len(parseCtx.Operations))
for loc, types := range parseCtx.TypeDefinitions {
fmt.Printf("Found %d types in %s\n", len(types), loc)
}
// Step 2: Create parser and generate code
parser, err := codegen.NewParser(cfg, parseCtx)
if err != nil {
panic(err)
}
codes, err := parser.Parse()
if err != nil {
panic(err)
}
// Write generated code
if err := os.WriteFile("generated.go", []byte(codes["all"]), 0644); err != nil {
panic(err)
}
fmt.Println("Code generated successfully to generated.go")
}
Configuration¶
Loading Configuration from YAML¶
package main
import (
"fmt"
"os"
"github.com/doordash-oss/oapi-codegen-dd/v3/pkg/codegen"
"go.yaml.in/yaml/v4"
)
func main() {
// Read OpenAPI spec
specContents, err := os.ReadFile("api.yaml")
if err != nil {
panic(err)
}
// Read configuration from YAML file
cfg := codegen.Configuration{}
cfgContents, err := os.ReadFile("config.yaml")
if err != nil {
panic(err)
}
if err = yaml.Unmarshal(cfgContents, &cfg); err != nil {
panic(err)
}
// Apply defaults to fill in any missing values
cfg = cfg.WithDefaults()
fmt.Printf("Package name: %s\n", cfg.PackageName)
fmt.Printf("Output directory: %s\n", cfg.Output.Directory)
fmt.Printf("Generate client: %v\n", cfg.Generate.Client)
// Generate code
generatedCode, err := codegen.Generate(specContents, cfg)
if err != nil {
panic(err)
}
// Write to file
if err := os.WriteFile("generated.go", []byte(generatedCode["all"]), 0644); err != nil {
panic(err)
}
fmt.Println("Code generated successfully to generated.go")
}
See the Configuration page for all available options.
Accessing Type Definitions¶
The ParseContext provides access to all generated type definitions and operations:
package main
import (
"fmt"
"os"
"github.com/doordash-oss/oapi-codegen-dd/v3/pkg/codegen"
)
func main() {
// Read OpenAPI spec
specContents, err := os.ReadFile("api.yaml")
if err != nil {
panic(err)
}
// Create configuration
cfg := codegen.NewDefaultConfiguration()
// Create parse context to access type definitions
parseCtx, errs := codegen.CreateParseContext(specContents, cfg)
if len(errs) > 0 {
panic(fmt.Sprintf("parsing spec: %v", errs[0]))
}
// Access operations
fmt.Printf("=== Operations (%d) ===\n", len(parseCtx.Operations))
for _, op := range parseCtx.Operations {
fmt.Printf("- %s %s (OperationID: %s)\n", op.Method, op.Path, op.ID)
if op.Body != nil {
fmt.Printf(" Request: %s\n", op.Body.Name)
}
for code, resp := range op.Response.All {
fmt.Printf(" Response %d: %s\n", code, resp.ResponseName)
}
}
// Access type definitions
fmt.Printf("\n=== Type Definitions ===\n")
for loc, types := range parseCtx.TypeDefinitions {
fmt.Printf("\n%s (%d types):\n", loc, len(types))
for _, td := range types {
fmt.Printf("- %s (GoType: %s)\n", td.Name, td.Schema.GoType)
if td.Schema.Description != "" {
fmt.Printf(" Description: %s\n", td.Schema.Description)
}
if td.IsAlias() {
fmt.Printf(" Is alias: true\n")
}
if td.IsOptional() {
fmt.Printf(" Is optional: true\n")
}
}
}
// Use TypeTracker to look up types
fmt.Printf("\n=== TypeTracker Lookups ===\n")
userRef := "#/components/schemas/User"
if typeName, ok := parseCtx.TypeTracker.LookupByRef(userRef); ok {
fmt.Printf("Found type for ref %s: %s\n", userRef, typeName)
}
if typeDef, ok := parseCtx.TypeTracker.LookupByName("User"); ok {
fmt.Printf("Found type by name 'User': %s (GoType: %s)\n",
typeDef.Name, typeDef.Schema.GoType)
}
}
TypeDefinition Structure¶
Each TypeDefinition describes a Go type in the generated code:
type TypeDefinition struct {
Name string // Go type name (e.g., "User")
JsonName string // JSON field name (e.g., "user")
Schema GoSchema // Schema object with type information
SpecLocation SpecLocation // Where in spec this was defined
NeedsMarshaler bool // Whether custom marshaler needed
HasSensitiveData bool // Whether has sensitive properties
}
TypeDefinition Methods¶
// Check if type is an alias
if td.IsAlias() {
fmt.Printf("%s is an alias\n", td.Name)
}
// Check if type is optional
if td.IsOptional() {
fmt.Printf("%s is optional\n", td.Name)
}
// Generate error response code (for response types)
if td.SpecLocation == codegen.SpecLocationResponse {
errorCode := td.GetErrorResponse()
fmt.Printf("Error response code: %s\n", errorCode)
}
SpecLocation Constants¶
Types are organized by where they appear in the OpenAPI spec:
const (
SpecLocationPath SpecLocation = "path" // Path parameters
SpecLocationQuery SpecLocation = "query" // Query parameters
SpecLocationHeader SpecLocation = "header" // Header parameters
SpecLocationBody SpecLocation = "body" // Request body
SpecLocationResponse SpecLocation = "response" // Response types
SpecLocationSchema SpecLocation = "schema" // Component schemas
SpecLocationUnion SpecLocation = "union" // Union types
)
Template Functions¶
The codegen.TemplateFunctions provides built-in template functions that can be extended with custom functions:
import (
"text/template"
"github.com/doordash-oss/oapi-codegen-dd/v3/pkg/codegen"
)
// Get built-in template functions
funcMap := codegen.TemplateFunctions
// Add custom functions
funcMap["myCustomFunc"] = func(s string) string {
return "custom: " + s
}
// Use in template
tmpl := template.New("custom").Funcs(funcMap)
tmpl.Parse("{{ myCustomFunc .Name }}")
Complete Example¶
Here's a complete example showing how to use the API in a real project:
package main
import (
"fmt"
"os"
"path/filepath"
"github.com/doordash-oss/oapi-codegen-dd/v3/pkg/codegen"
"go.yaml.in/yaml/v4"
)
func main() {
// Read OpenAPI spec
specContents, err := os.ReadFile("api.yaml")
if err != nil {
panic(fmt.Sprintf("reading spec: %v", err))
}
// Read configuration from YAML
cfg := codegen.Configuration{}
cfgContents, err := os.ReadFile("config.yaml")
if err != nil {
panic(fmt.Sprintf("reading config: %v", err))
}
if err = yaml.Unmarshal(cfgContents, &cfg); err != nil {
panic(fmt.Sprintf("parsing config: %v", err))
}
cfg = cfg.WithDefaults()
// Create parse context
parseCtx, errs := codegen.CreateParseContext(specContents, cfg)
if len(errs) > 0 {
panic(fmt.Sprintf("parsing spec: %v", errs[0]))
}
// Log what we found
fmt.Printf("Parsed OpenAPI spec:\n")
fmt.Printf(" Operations: %d\n", len(parseCtx.Operations))
for loc, types := range parseCtx.TypeDefinitions {
fmt.Printf(" Types in %s: %d\n", loc, len(types))
}
// Create parser
parser, err := codegen.NewParser(cfg, parseCtx)
if err != nil {
panic(fmt.Sprintf("creating parser: %v", err))
}
// Generate code
codes, err := parser.Parse()
if err != nil {
panic(fmt.Sprintf("generating code: %v", err))
}
// Create output directory if it doesn't exist
if err := os.MkdirAll(cfg.Output.Directory, 0755); err != nil {
panic(fmt.Sprintf("creating output directory: %v", err))
}
// Write generated files
if cfg.Output.UseSingleFile {
// Single file output
outPath := filepath.Join(cfg.Output.Directory, cfg.Output.Filename)
formatted, err := codegen.FormatCode(codes["all"])
if err != nil {
panic(fmt.Sprintf("formatting code: %v", err))
}
if err := os.WriteFile(outPath, []byte(formatted), 0644); err != nil {
panic(fmt.Sprintf("writing file: %v", err))
}
fmt.Printf("\nGenerated code written to: %s\n", outPath)
} else {
// Multiple file output
for name, code := range codes {
if name == "all" {
continue // Skip the combined output
}
outPath := filepath.Join(cfg.Output.Directory, name+".go")
formatted, err := codegen.FormatCode(code)
if err != nil {
panic(fmt.Sprintf("formatting %s: %v", name, err))
}
if err := os.WriteFile(outPath, []byte(formatted), 0644); err != nil {
panic(fmt.Sprintf("writing %s: %v", name, err))
}
fmt.Printf("Generated: %s\n", outPath)
}
}
fmt.Println("\nCode generation complete!")
}
See Also¶
- Configuration - Complete configuration reference
- Union Types - Working with union types
- Extensions - OpenAPI extensions reference