ogen

website·Indexed 32 days ago

https://ogen.dev

A powerful and fast OpenAPI v3 code generator for Go. It features a static radix tree-based router, optimized code-generated JSON encoding using go-faster/jx to avoid reflection, and support for complex OpenAPI types including sum types and generic wrappers for optional fields.

Tokens
9.8K
Snippets
57
Records
72
Agent score
66%

What's inside ogen

  1. Understand the ogen request lifecycle

    When a request hits an ogen server, it follows this sequence:

    1. net/http Middleware: The request passes through any standard net/http middleware wrapping the generated Server.
    2. Routing: The Server routes the request to the specific operation handler.
    3. OpenTelemetry: A trace is created and the request counter is incremented.
    4. Security Handlers: Security requirements are validated. If the OpenAPI spec defines security schemes, ogen generates a SecurityHandler interface. The handler validates the presence and correctness of security values. If requirements are not met, it returns ogenerrors.ErrSecurityRequirementIsNotSatisfied or a ogenerrors.SecurityError.
    5. Parameters Decoding: Request parameters are decoded. Failures result in a ogenerrors.DecodeParamsError.
    6. Request Body Decoding: The Content-Type is matched against the spec. If no match is found, a validate.InvalidContentTypeError is generated. Decoding failures result in a ogenerrors.DecodeRequestError.
    7. User Handler: The user-defined Handler is executed. If WithMiddleware options were provided, the handler is passed as the next parameter to that middleware.
    8. Error Handling: If any step fails, the server increments the error counter, sets the trace status to error, and records the error via OpenTelemetry Span.RecordError.
  2. Generate a type-safe Go client and server from OpenAPI v3

    ogen generates a statically typed Go client and server directly from an OpenAPI v3 specification. It eliminates boilerplate by generating structures for path arguments, headers, and query parameters. It maps OpenAPI string formats like uuid, date, date-time, and uri directly to Go types. The server implementation requires implementing a single Handler interface, and it uses a code-generated static radix router for high-performance request dispatch.
    # Example OpenAPI v3 spec (petstore.yml)
    paths:
      /pet/{petId}:
        get:
          operationId: getPetById
          parameters:
            - name: petId
              in: path
              required: true
              schema:
                type: integer
                format: int64
          responses:
            '200':
              description: Pet
              content:
                application/json:
                  schema:
                    $ref: '#/components/schemas/Pet'
    // Generated Go code
    
    // Pet describes #/components/schemas/Pet.
    type Pet struct {
        ID   int64     `json:"id"`
        Name string    `json:"name"`
        Tag  OptUUID   `json:"tag"`
    }
    
    // Handler handles operations described
    // by OpenAPI v3 specification.
    type Handler interface {
        // GET /pet/{petId}
        GetPetById(ctx context.Context, params GetPetByIdParams) (*Pet, error)
    }
  3. Handle optional and nullable types with ogen generic wrappers

    Instead of using pointers, ogen generates generic wrappers to represent optional and nullable fields. For example, an OptNilString tracks whether a string was provided (Set) and whether that value is explicitly null (Null).
    // OptNilString is optional nullable string.
    type OptNilString struct {
        Value string
        Set   bool
        Null  bool
    }
  4. Handle untyped and empty schemas with jx.Raw

    In ogen, untyped and empty schemas are represented using the jx.Raw type from the github.com/go-faster/jx package. This allows the application to handle JSON data that does not conform to a specific predefined schema.
  5. Represent Sum types (tagged unions) in ogen

    ogen uses Sum types to represent OpenAPI oneOf and some anyOf schemas. A Sum type is a structure that can be one of multiple possible variants. The generated Go code includes a discriminator field (e.g., Type) that allows developers to switch between the possible variants.
    # OpenAPI Schema
    Sum:
      oneOf:
        - $ref: '#/components/schemas/Cat'
        - $ref: '#/components/schemas/Dog'
    // Generated Go code
    type Sum struct {
    	Type SumType // switch on this field
    	Cat  Cat
    	Dog  Dog
    }
    
    type SumType string
    
    const (
    	CatSum SumType = "Cat"
    	DogSum SumType = "Dog"
    )
  6. Ogen core features and technical architecture

    Ogen is a statically typed client and server generator based on OpenAPI v3 specifications. Key technical features include:

    • Performance: Uses code-generated JSON encoding via go-faster/jx to avoid reflection and interface{}/any for speed and to overcome encoding/json limitations.
    • Routing: Implements a code-generated static radix router.
    • Type Safety: Generates Go structures for arguments, headers, and URL queries. String formats such as uuid, date, date-time, and uri are mapped directly to Go types.
    • Validation: Validation logic is code-generated according to the specification.
    • Observability: Built-in support for OpenTelemetry tracing and metrics.
  7. Handle security requirements in ogen

    If an OpenAPI specification defines security requirements, ogen generates a SecurityHandler interface. For every defined security scheme, the operation handler:

    1. Checks for the presence of the required value.
    2. Parses and validates the value, then calls the corresponding method in the SecurityHandler implementation.
    3. If validation fails, it generates a ogenerrors.SecurityError and triggers the error handler.

    After processing all schemes, ogen verifies if the combined satisfied schemes meet at least one of the security requirements defined in the spec. If not, it returns ogenerrors.ErrSecurityRequirementIsNotSatisfied.

    Note: The SecurityHandler methods are called for every present scheme value, even if a requirement has already been satisfied, to ensure all provided credentials are validated.

    {
      "security": [
        {
          "basicAuth": [],
          "headerKey": []
        },
        {
          "bearerToken": [],
          "headerKey": []
        }
      ],
      "components": {
        "securitySchemes": {
          "headerKey": {
            "type": "apiKey",
            "in": "header",
            "name": "X-Api-Key"
          },
          "basicAuth": {
            "type": "http",
            "scheme": "basic"
          },
          "bearerToken": {
            "type": "http",
            "scheme": "bearer"
          }
        }
      }
    }
  8. Map OpenAPI schema to ogen Go types

    ogen maps OpenAPI schema requirements to specific Go types and decoder behaviors based on whether a field is required, optional, or nullable.
    Schema                 | Type         | No Value | Null     | Value
    ---------------------- | ------------ | -------- | -------- | --------
    required               | T            | Error    | Error    | OK
    optional               | Opt[T]       | Set=false| Error    | Set=true
    nullable, but required | Nil[T]       | Error    | Null=true| OK
    optional & nullable    | OptNil[T]    | Set=false| Set=true,Null=true | Set=true,Null=false
  9. Install ogen as a Go tool dependency

    To use ogen for reproducible builds, add it as a tool dependency to your Go module. This requires Go 1.24+ and records the tool in the tool directive of your go.mod file.
    # Initialize the Go module
    go mod init <project>
    
    # Add ogen as a tool dependency
    go get -tool github.com/ogen-go/ogen/cmd/ogen@latest
  10. Generate Go code from an OpenAPI v3 specification

    Use the go generate tool to run ogen and produce Go code from an OpenAPI YAML file. You must create a .go file containing the //go:generate directive specifying the target package name and the source specification file.
    package project
    
    //go:generate go tool ogen --target petstore --clean petstore.yml
    # Run the generator from the module root
    go generate ./...
  11. Apply net/http middleware to all routes

    Because the api.Server type implements the http.Handler interface, you can wrap the entire server in any standard net/http compatible middleware.
    func main() {
    	srv, err := api.NewServer(myHandler{})
    	if err != nil {
    		panic(err)
    	}
    	// Wrap the server with a middleware (e.g., GzipHandler)
    	http.ListenAndServe(":8080", gzhttp.GzipHandler(srv))
    }
  12. Implement custom logic using ogen high-level middleware

    The ogen high-level middleware API allows developers to execute custom logic before and after a request is handled. Unlike standard net/http middleware, ogen middleware receives a request that has already been parsed and validated, and it must return a typed response. Note that this API is currently experimental and subject to change.
    package middlewares
    
    import (
    	"go.uber.org/zap"
    	"go.uber.org/zap/zapcore"
    	"github.com/ogen-go/ogen/middleware"
    )
    
    func Logging(logger *zap.Logger) middleware.Middleware {
    	return func(
    		req middleware.Request,
    		next func(req middleware.Request) (middleware.Response, error),
    	) (middleware.Response, error) {
    		logger := logger.With(
    			zap.String("operation", req.OperationName),
    			zap.String("operationId", req.OperationID),
    		)
    		logger.Info("Handling request")
    		resp, err := next(req)
    		if err != nil {
    			logger.Error("Fail", zap.Error(err))
    		} else {
    			var fields []zapcore.Field
    			// Check if the response type provides a status code
    			if tresp, ok := resp.Type.(interface{ GetStatusCode() int }); ok {
    				fields = []zapcore.Field{
    					zap.Int("status_code", tresp.GetStatusCode()),
    				}
    			}
    			logger.Info("Success", fields...)
    		}
    		return resp, err
    	}
    }