Skip to content

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:

examples/api/simple-generate/main.go
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():

examples/api/advanced-two-step/main.go
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

examples/api/load-config/main.go
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:

examples/api/access-types/main.go
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:

examples/api/complete-example/main.go
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