OpenAPI Specification
Learn how to generate OpenAPI specifications from Go code with automatic parameter discovery and schema generation
The Rivaas OpenAPI package provides automatic OpenAPI 3.0.4 and 3.1.2 specification generation from Go code. Uses struct tags and reflection with a clean, type-safe API. Minimal boilerplate required.
Features
- Clean API - Builder-style
API.Generate() method for specification generation - Type-Safe Version Selection -
V30x and V31x constants with IDE autocomplete - Fluent HTTP Method Constructors -
GET(), POST(), PUT(), etc. for clean operation definitions - Functional Options - Consistent
With* pattern for all configuration - Type-Safe Warning Diagnostics -
diag package for fine-grained warning control - Automatic Parameter Discovery - Extracts query, path, header, and cookie parameters from struct tags
- Schema Generation - Converts Go types to OpenAPI schemas automatically
- Swagger UI Configuration - Built-in, customizable Swagger UI settings
- Semantic Operation IDs - Auto-generates operation IDs from HTTP methods and paths
- Security Schemes - Support for Bearer, API Key, OAuth2, and OpenID Connect
- Collision-Resistant Naming - Schema names use
pkgname.TypeName format to prevent collisions - Built-in Validation - Validates generated specs against official OpenAPI meta-schemas
- Standalone Validator - Validate external OpenAPI specs with pre-compiled schemas
Quick Start
Here’s a 30-second example to get you started:
package main
import (
"context"
"fmt"
"log"
"rivaas.dev/openapi"
)
type User struct {
ID int `json:"id"`
Name string `json:"name"`
Email string `json:"email"`
}
type CreateUserRequest struct {
Name string `json:"name" validate:"required"`
Email string `json:"email" validate:"required,email"`
}
func main() {
api := openapi.MustNew(
openapi.WithTitle("My API", "1.0.0"),
openapi.WithInfoDescription("API for managing users"),
openapi.WithServer("http://localhost:8080", "Local development"),
openapi.WithBearerAuth("bearerAuth", "JWT authentication"),
)
result, err := api.Generate(context.Background(),
openapi.GET("/users/:id",
openapi.WithSummary("Get user"),
openapi.WithResponse(200, User{}),
openapi.WithSecurity("bearerAuth"),
),
openapi.POST("/users",
openapi.WithSummary("Create user"),
openapi.WithRequest(CreateUserRequest{}),
openapi.WithResponse(201, User{}),
),
openapi.DELETE("/users/:id",
openapi.WithSummary("Delete user"),
openapi.WithResponse(204, nil),
openapi.WithSecurity("bearerAuth"),
),
)
if err != nil {
log.Fatal(err)
}
// Check for warnings (optional)
if len(result.Warnings) > 0 {
fmt.Printf("Generated with %d warnings\n", len(result.Warnings))
}
fmt.Println(string(result.JSON))
}
How It Works
- API configuration is done through functional options with
With* prefix - Operations are defined using HTTP method constructors:
GET(), POST(), etc. - Types are automatically converted to OpenAPI schemas using reflection
- Parameters are discovered from struct tags:
path, query, header, cookie - Validation is optional but recommended for production use
Learning Path
Follow these guides to master OpenAPI specification generation with Rivaas:
- Installation - Get started with the openapi package
- Basic Usage - Learn the fundamentals of generating specifications
- Configuration - Configure API info, servers, and version selection
- Security - Add authentication and authorization schemes
- Operations - Define HTTP operations with methods and options
- Auto-Discovery - Use struct tags for automatic parameter discovery
- Schema Generation - Understand Go type to OpenAPI schema conversion
- Swagger UI - Customize the Swagger UI interface
- Validation - Validate generated specifications
- Diagnostics - Handle warnings with type-safe diagnostics
- Advanced Usage - Extensions, custom operation IDs, and strict mode
- Examples - See real-world usage patterns
Next Steps
1 - Installation
Install and set up the OpenAPI package for Go
Get started with the OpenAPI package by adding it to your Go project.
Prerequisites
- Go 1.25 or higher - The package requires Go 1.25+
- Go modules - Your project should use Go modules for dependency management
Installation
Install the package using go get:
go get rivaas.dev/openapi
This will download the latest version of the package and add it to your go.mod file.
Verifying Installation
Create a simple test file to verify the installation:
package main
import (
"context"
"fmt"
"log"
"rivaas.dev/openapi"
)
type User struct {
ID int `json:"id"`
Name string `json:"name"`
}
func main() {
api := openapi.MustNew(
openapi.WithTitle("Test API", "1.0.0"),
)
result, err := api.Generate(context.Background(),
openapi.GET("/users/:id",
openapi.WithSummary("Get user"),
openapi.WithResponse(200, User{}),
),
)
if err != nil {
log.Fatal(err)
}
fmt.Println("OpenAPI spec generated successfully!")
fmt.Printf("JSON spec: %d bytes\n", len(result.JSON))
}
Run the test:
If you see “OpenAPI spec generated successfully!”, the package is installed correctly.
Sub-packages
The OpenAPI package includes two sub-packages that are automatically available when you install the main package:
Diagnostics (diag)
Type-safe warning handling:
import "rivaas.dev/openapi/diag"
Validator (validate)
Standalone specification validator for validating external OpenAPI specs:
import "rivaas.dev/openapi/validate"
Updating
To update to the latest version:
go get -u rivaas.dev/openapi
Next Steps
2 - Basic Usage
Learn the fundamentals of generating OpenAPI specifications
Learn how to generate OpenAPI specifications from Go code using the openapi package.
Creating an API Configuration
The first step is to create an API configuration using New() or MustNew():
import "rivaas.dev/openapi"
// With error handling
api, err := openapi.New(
openapi.WithTitle("My API", "1.0.0"),
openapi.WithInfoDescription("API description"),
)
if err != nil {
log.Fatal(err)
}
// Without error handling (panics on error)
api := openapi.MustNew(
openapi.WithTitle("My API", "1.0.0"),
openapi.WithInfoDescription("API description"),
)
The MustNew() function is convenient for initialization code. Use it where panicking on error is acceptable.
Generating Specifications
Use api.Generate() with a context and variadic operation arguments:
result, err := api.Generate(context.Background(),
openapi.GET("/users",
openapi.WithSummary("List users"),
openapi.WithResponse(200, []User{}),
),
openapi.GET("/users/:id",
openapi.WithSummary("Get user"),
openapi.WithResponse(200, User{}),
),
openapi.POST("/users",
openapi.WithSummary("Create user"),
openapi.WithRequest(CreateUserRequest{}),
openapi.WithResponse(201, User{}),
),
)
if err != nil {
log.Fatal(err)
}
The Result Object
The Generate() method returns a Result object containing:
JSON - The OpenAPI specification as JSON bytes.YAML - The OpenAPI specification as YAML bytes.Warnings - Any generation warnings. See Diagnostics for details.
// Use the JSON specification
fmt.Println(string(result.JSON))
// Or use the YAML specification
fmt.Println(string(result.YAML))
// Check for warnings
if len(result.Warnings) > 0 {
fmt.Printf("Generated with %d warnings\n", len(result.Warnings))
}
Defining Operations
Operations are defined using HTTP method constructors:
openapi.GET("/path", options...)
openapi.POST("/path", options...)
openapi.PUT("/path", options...)
openapi.PATCH("/path", options...)
openapi.DELETE("/path", options...)
openapi.HEAD("/path", options...)
openapi.OPTIONS("/path", options...)
openapi.TRACE("/path", options...)
Each constructor takes a path and optional operation options.
Path Parameters
Use colon syntax for path parameters:
openapi.GET("/users/:id",
openapi.WithSummary("Get user by ID"),
openapi.WithResponse(200, User{}),
)
openapi.GET("/orgs/:orgId/users/:userId",
openapi.WithSummary("Get user in organization"),
openapi.WithResponse(200, User{}),
)
Path parameters are automatically discovered and marked as required.
Request and Response Types
Define request and response types using Go structs:
type User struct {
ID int `json:"id"`
Name string `json:"name"`
Email string `json:"email"`
}
type CreateUserRequest struct {
Name string `json:"name"`
Email string `json:"email"`
}
// Use in operations
openapi.POST("/users",
openapi.WithRequest(CreateUserRequest{}),
openapi.WithResponse(201, User{}),
)
The package automatically converts Go types to OpenAPI schemas.
Multiple Response Types
Operations can have multiple response types for different status codes:
type ErrorResponse struct {
Code int `json:"code"`
Message string `json:"message"`
}
openapi.GET("/users/:id",
openapi.WithSummary("Get user"),
openapi.WithResponse(200, User{}),
openapi.WithResponse(404, ErrorResponse{}),
openapi.WithResponse(500, ErrorResponse{}),
)
Empty Responses
For responses with no body, use nil:
openapi.DELETE("/users/:id",
openapi.WithSummary("Delete user"),
openapi.WithResponse(204, nil),
)
Complete Example
Here’s a complete example putting it all together:
package main
import (
"context"
"fmt"
"log"
"os"
"rivaas.dev/openapi"
)
type User struct {
ID int `json:"id"`
Name string `json:"name"`
Email string `json:"email"`
}
type CreateUserRequest struct {
Name string `json:"name"`
Email string `json:"email"`
}
type ErrorResponse struct {
Code int `json:"code"`
Message string `json:"message"`
}
func main() {
api := openapi.MustNew(
openapi.WithTitle("User API", "1.0.0"),
openapi.WithInfoDescription("API for managing users"),
openapi.WithServer("http://localhost:8080", "Local development"),
)
result, err := api.Generate(context.Background(),
openapi.GET("/users",
openapi.WithSummary("List users"),
openapi.WithResponse(200, []User{}),
),
openapi.GET("/users/:id",
openapi.WithSummary("Get user"),
openapi.WithResponse(200, User{}),
openapi.WithResponse(404, ErrorResponse{}),
),
openapi.POST("/users",
openapi.WithSummary("Create user"),
openapi.WithRequest(CreateUserRequest{}),
openapi.WithResponse(201, User{}),
openapi.WithResponse(400, ErrorResponse{}),
),
openapi.DELETE("/users/:id",
openapi.WithSummary("Delete user"),
openapi.WithResponse(204, nil),
),
)
if err != nil {
log.Fatal(err)
}
// Write to file
if err := os.WriteFile("openapi.json", result.JSON, 0644); err != nil {
log.Fatal(err)
}
fmt.Println("OpenAPI specification written to openapi.json")
}
Next Steps
3 - Configuration
Configure API metadata, version selection, servers, and tags
Learn how to configure your OpenAPI specification with metadata, servers, and version selection.
Basic Configuration
Configuration is done exclusively through functional options with With* prefix:
api := openapi.MustNew(
openapi.WithTitle("My API", "1.0.0"),
openapi.WithInfoDescription("API description"),
openapi.WithInfoSummary("Short summary"), // 3.1.x only
openapi.WithTermsOfService("https://example.com/terms"),
)
Required Configuration
Only WithTitle() is required when creating an API configuration:
// Minimal configuration
api := openapi.MustNew(
openapi.WithTitle("My API", "1.0.0"),
)
Add contact information for API support:
api := openapi.MustNew(
openapi.WithTitle("My API", "1.0.0"),
openapi.WithContact(
"API Support",
"https://example.com/support",
"support@example.com",
),
)
Specify the API license:
api := openapi.MustNew(
openapi.WithTitle("My API", "1.0.0"),
openapi.WithLicense(
"Apache 2.0",
"https://www.apache.org/licenses/LICENSE-2.0.html",
),
)
Version Selection
The package supports two OpenAPI version families:
// Target OpenAPI 3.0.x (generates 3.0.4)
api := openapi.MustNew(
openapi.WithTitle("API", "1.0.0"),
openapi.WithVersion(openapi.V30x), // Default
)
// Target OpenAPI 3.1.x (generates 3.1.2)
api := openapi.MustNew(
openapi.WithTitle("API", "1.0.0"),
openapi.WithVersion(openapi.V31x),
)
The constants V30x and V31x represent version families. Internally they map to specific versions. 3.0.4 and 3.1.2 are used in the generated specification.
Version-Specific Features
Some features are only available in OpenAPI 3.1.x:
WithInfoSummary() - Short summary for the APIWithLicenseIdentifier() - SPDX license identifier- Webhooks support
- Mutual TLS authentication
When using these features with a 3.0.x target, the package will generate warnings (see Diagnostics).
Servers
Add server configurations to specify where the API is available:
api := openapi.MustNew(
openapi.WithTitle("My API", "1.0.0"),
openapi.WithServer("https://api.example.com", "Production"),
openapi.WithServer("https://staging.example.com", "Staging"),
openapi.WithServer("http://localhost:8080", "Local development"),
)
Server Variables
Add variables to server URLs for flexible configuration:
api := openapi.MustNew(
openapi.WithTitle("My API", "1.0.0"),
openapi.WithServer("https://{environment}.example.com", "Environment-based"),
openapi.WithServerVariable("environment", "api",
[]string{"api", "staging", "dev"},
"Environment to use",
),
)
Multiple variables can be defined for a single server:
api := openapi.MustNew(
openapi.WithTitle("My API", "1.0.0"),
openapi.WithServer("https://{subdomain}.{domain}", "Custom domain"),
openapi.WithServerVariable("subdomain", "api",
[]string{"api", "staging"},
"Subdomain",
),
openapi.WithServerVariable("domain", "example.com",
[]string{"example.com", "test.com"},
"Domain",
),
)
Tags help organize operations in the documentation:
api := openapi.MustNew(
openapi.WithTitle("My API", "1.0.0"),
openapi.WithTag("users", "User management operations"),
openapi.WithTag("posts", "Post management operations"),
openapi.WithTag("auth", "Authentication operations"),
)
Tags are then referenced in operations:
result, err := api.Generate(context.Background(),
openapi.GET("/users",
openapi.WithSummary("List users"),
openapi.WithTags("users"),
openapi.WithResponse(200, []User{}),
),
openapi.GET("/posts",
openapi.WithSummary("List posts"),
openapi.WithTags("posts"),
openapi.WithResponse(200, []Post{}),
),
)
External Documentation
Link to external documentation:
api := openapi.MustNew(
openapi.WithTitle("My API", "1.0.0"),
openapi.WithExternalDocs(
"https://docs.example.com",
"Full API Documentation",
),
)
Complete Configuration Example
Here’s a complete example with all common configuration options:
package main
import (
"context"
"log"
"rivaas.dev/openapi"
)
func main() {
api := openapi.MustNew(
// Basic info
openapi.WithTitle("User Management API", "2.1.0"),
openapi.WithInfoDescription("Comprehensive API for managing users and their profiles"),
openapi.WithTermsOfService("https://example.com/terms"),
// Contact
openapi.WithContact(
"API Support Team",
"https://example.com/support",
"api-support@example.com",
),
// License
openapi.WithLicense(
"Apache 2.0",
"https://www.apache.org/licenses/LICENSE-2.0.html",
),
// Version selection
openapi.WithVersion(openapi.V31x),
// Servers
openapi.WithServer("https://api.example.com", "Production"),
openapi.WithServer("https://staging-api.example.com", "Staging"),
openapi.WithServer("http://localhost:8080", "Local development"),
// Tags
openapi.WithTag("users", "User management operations"),
openapi.WithTag("profiles", "User profile operations"),
openapi.WithTag("auth", "Authentication and authorization"),
// External docs
openapi.WithExternalDocs(
"https://docs.example.com/api",
"Complete API Documentation",
),
// Security schemes (covered in detail in Security guide)
openapi.WithBearerAuth("bearerAuth", "JWT authentication"),
)
result, err := api.Generate(context.Background(),
// ... operations here
)
if err != nil {
log.Fatal(err)
}
// Use result...
}
Next Steps
4 - Security
Add authentication and authorization schemes to your OpenAPI specification
Learn how to add security schemes to your OpenAPI specification for authentication and authorization.
Security Scheme Types
The package supports four types of security schemes:
- Bearer Authentication - JWT or token-based authentication.
- API Key - API keys in headers, query parameters, or cookies.
- OAuth2 - OAuth 2.0 authorization flows.
- OpenID Connect - OpenID Connect authentication.
Bearer Authentication
Bearer authentication is commonly used for JWT tokens:
api := openapi.MustNew(
openapi.WithTitle("My API", "1.0.0"),
openapi.WithBearerAuth("bearerAuth", "JWT authentication"),
)
Using Bearer Authentication in Operations
result, err := api.Generate(context.Background(),
openapi.GET("/users",
openapi.WithSummary("List users"),
openapi.WithSecurity("bearerAuth"),
openapi.WithResponse(200, []User{}),
),
)
The generated specification will expect an Authorization: Bearer <token> header.
API Key Authentication
API keys can be placed in headers, query parameters, or cookies:
api := openapi.MustNew(
openapi.WithTitle("My API", "1.0.0"),
openapi.WithAPIKey(
"apiKey",
"X-API-Key",
openapi.InHeader,
"API key for authentication",
),
)
Query Parameter API Key
api := openapi.MustNew(
openapi.WithTitle("My API", "1.0.0"),
openapi.WithAPIKey(
"apiKey",
"api_key",
openapi.InQuery,
"API key for authentication",
),
)
Cookie-Based API Key
api := openapi.MustNew(
openapi.WithTitle("My API", "1.0.0"),
openapi.WithAPIKey(
"apiKey",
"api_key",
openapi.InCookie,
"API key for authentication",
),
)
Using API Key in Operations
result, err := api.Generate(context.Background(),
openapi.GET("/users",
openapi.WithSummary("List users"),
openapi.WithSecurity("apiKey"),
openapi.WithResponse(200, []User{}),
),
)
OAuth2
OAuth2 supports multiple flows: authorization code, implicit, password, and client credentials.
Authorization Code Flow
api := openapi.MustNew(
openapi.WithTitle("My API", "1.0.0"),
openapi.WithOAuth2(
"oauth2",
"OAuth2 authentication",
openapi.OAuth2Flow{
Type: openapi.FlowAuthorizationCode,
AuthorizationURL: "https://example.com/oauth/authorize",
TokenURL: "https://example.com/oauth/token",
Scopes: map[string]string{
"read": "Read access to resources",
"write": "Write access to resources",
"admin": "Administrative access",
},
},
),
)
Implicit Flow
api := openapi.MustNew(
openapi.WithTitle("My API", "1.0.0"),
openapi.WithOAuth2(
"oauth2",
"OAuth2 authentication",
openapi.OAuth2Flow{
Type: openapi.FlowImplicit,
AuthorizationURL: "https://example.com/oauth/authorize",
Scopes: map[string]string{
"read": "Read access",
"write": "Write access",
},
},
),
)
Password Flow
api := openapi.MustNew(
openapi.WithTitle("My API", "1.0.0"),
openapi.WithOAuth2(
"oauth2",
"OAuth2 authentication",
openapi.OAuth2Flow{
Type: openapi.FlowPassword,
TokenURL: "https://example.com/oauth/token",
Scopes: map[string]string{
"read": "Read access",
"write": "Write access",
},
},
),
)
Client Credentials Flow
api := openapi.MustNew(
openapi.WithTitle("My API", "1.0.0"),
openapi.WithOAuth2(
"oauth2",
"OAuth2 authentication",
openapi.OAuth2Flow{
Type: openapi.FlowClientCredentials,
TokenURL: "https://example.com/oauth/token",
Scopes: map[string]string{
"api": "API access",
},
},
),
)
Using OAuth2 in Operations
Specify which scopes are required for an operation:
result, err := api.Generate(context.Background(),
openapi.GET("/users",
openapi.WithSummary("List users"),
openapi.WithSecurity("oauth2", "read"),
openapi.WithResponse(200, []User{}),
),
openapi.POST("/users",
openapi.WithSummary("Create user"),
openapi.WithSecurity("oauth2", "read", "write"),
openapi.WithRequest(CreateUserRequest{}),
openapi.WithResponse(201, User{}),
),
)
OpenID Connect
OpenID Connect provides authentication on top of OAuth2:
api := openapi.MustNew(
openapi.WithTitle("My API", "1.0.0"),
openapi.WithOpenIDConnect(
"openId",
"https://example.com/.well-known/openid-configuration",
"OpenID Connect authentication",
),
)
Using OpenID Connect in Operations
result, err := api.Generate(context.Background(),
openapi.GET("/users",
openapi.WithSummary("List users"),
openapi.WithSecurity("openId"),
openapi.WithResponse(200, []User{}),
),
)
Multiple Security Schemes
You can define multiple security schemes:
api := openapi.MustNew(
openapi.WithTitle("My API", "1.0.0"),
openapi.WithBearerAuth("bearerAuth", "JWT authentication"),
openapi.WithAPIKey(
"apiKey",
"X-API-Key",
openapi.InHeader,
"API key authentication",
),
)
Alternative Security Requirements (OR)
Allow multiple authentication methods for a single operation:
result, err := api.Generate(context.Background(),
openapi.GET("/users",
openapi.WithSummary("List users"),
openapi.WithSecurity("bearerAuth"), // Can use bearer auth
openapi.WithSecurity("apiKey"), // OR can use API key
openapi.WithResponse(200, []User{}),
),
)
This means the client can authenticate using either bearer auth or an API key.
Optional vs Required Security
Required Security
Apply security at the operation level:
openapi.GET("/users",
openapi.WithSecurity("bearerAuth"),
openapi.WithResponse(200, []User{}),
)
Optional Security (Public Endpoint)
Omit the WithSecurity() option:
openapi.GET("/public/status",
openapi.WithSummary("Public status endpoint"),
openapi.WithResponse(200, StatusResponse{}),
)
Complete Security Example
Here’s a complete example with multiple security schemes:
package main
import (
"context"
"log"
"rivaas.dev/openapi"
)
type User struct {
ID int `json:"id"`
Name string `json:"name"`
}
type CreateUserRequest struct {
Name string `json:"name"`
}
func main() {
api := openapi.MustNew(
openapi.WithTitle("Secure API", "1.0.0"),
// Define multiple security schemes
openapi.WithBearerAuth("bearerAuth", "JWT token authentication"),
openapi.WithAPIKey(
"apiKey",
"X-API-Key",
openapi.InHeader,
"API key authentication",
),
openapi.WithOAuth2(
"oauth2",
"OAuth2 authentication",
openapi.OAuth2Flow{
Type: openapi.FlowAuthorizationCode,
AuthorizationURL: "https://example.com/oauth/authorize",
TokenURL: "https://example.com/oauth/token",
Scopes: map[string]string{
"read": "Read access",
"write": "Write access",
},
},
),
)
result, err := api.Generate(context.Background(),
// Public endpoint (no security)
openapi.GET("/health",
openapi.WithSummary("Health check"),
openapi.WithResponse(200, nil),
),
// Bearer auth only
openapi.GET("/users",
openapi.WithSummary("List users"),
openapi.WithSecurity("bearerAuth"),
openapi.WithResponse(200, []User{}),
),
// API key or bearer auth (alternative)
openapi.GET("/users/:id",
openapi.WithSummary("Get user"),
openapi.WithSecurity("bearerAuth"),
openapi.WithSecurity("apiKey"),
openapi.WithResponse(200, User{}),
),
// OAuth2 with specific scopes
openapi.POST("/users",
openapi.WithSummary("Create user"),
openapi.WithSecurity("oauth2", "read", "write"),
openapi.WithRequest(CreateUserRequest{}),
openapi.WithResponse(201, User{}),
),
)
if err != nil {
log.Fatal(err)
}
// Use result...
}
Next Steps
5 - Operations
Define HTTP operations with methods, options, and composable configurations
Learn how to define HTTP operations using method constructors and operation options.
HTTP Method Constructors
The package provides HTTP method constructors for defining operations:
openapi.GET("/users/:id", opts...)
openapi.POST("/users", opts...)
openapi.PUT("/users/:id", opts...)
openapi.PATCH("/users/:id", opts...)
openapi.DELETE("/users/:id", opts...)
openapi.HEAD("/users/:id", opts...)
openapi.OPTIONS("/users", opts...)
openapi.TRACE("/debug", opts...)
Each constructor takes a path and optional operation options.
Operation Options
All operation options follow the With* naming convention:
| Function | Description |
|---|
WithSummary(s) | Set operation summary |
WithDescription(s) | Set operation description |
WithOperationID(id) | Set custom operation ID |
WithRequest(type, examples...) | Set request body type |
WithResponse(status, type, examples...) | Set response type for status code |
WithTags(tags...) | Add tags to operation |
WithSecurity(scheme, scopes...) | Add security requirement |
WithDeprecated() | Mark operation as deprecated |
WithConsumes(types...) | Set accepted content types |
WithProduces(types...) | Set returned content types |
WithOperationExtension(key, value) | Add operation extension |
Basic Operation Definition
Define a simple GET operation:
result, err := api.Generate(context.Background(),
openapi.GET("/users/:id",
openapi.WithSummary("Get user by ID"),
openapi.WithDescription("Retrieves a user by their unique identifier"),
openapi.WithResponse(200, User{}),
),
)
Request Bodies
Use WithRequest() to specify the request body type:
type CreateUserRequest struct {
Name string `json:"name"`
Email string `json:"email"`
}
openapi.POST("/users",
openapi.WithSummary("Create user"),
openapi.WithRequest(CreateUserRequest{}),
openapi.WithResponse(201, User{}),
)
Request with Examples
Provide example request bodies:
exampleUser := CreateUserRequest{
Name: "John Doe",
Email: "john@example.com",
}
openapi.POST("/users",
openapi.WithSummary("Create user"),
openapi.WithRequest(CreateUserRequest{}, exampleUser),
openapi.WithResponse(201, User{}),
)
Response Types
Define multiple response types for different status codes:
type ErrorResponse struct {
Code int `json:"code"`
Message string `json:"message"`
}
openapi.GET("/users/:id",
openapi.WithSummary("Get user"),
openapi.WithResponse(200, User{}),
openapi.WithResponse(404, ErrorResponse{}),
openapi.WithResponse(500, ErrorResponse{}),
)
Response with Examples
Provide example responses:
exampleUser := User{
ID: 123,
Name: "John Doe",
Email: "john@example.com",
}
openapi.GET("/users/:id",
openapi.WithSummary("Get user"),
openapi.WithResponse(200, User{}, exampleUser),
)
Organize operations with tags:
result, err := api.Generate(context.Background(),
openapi.GET("/users",
openapi.WithSummary("List users"),
openapi.WithTags("users"),
openapi.WithResponse(200, []User{}),
),
openapi.GET("/posts",
openapi.WithSummary("List posts"),
openapi.WithTags("posts"),
openapi.WithResponse(200, []Post{}),
),
)
Multiple tags per operation:
openapi.GET("/users/:id/posts",
openapi.WithSummary("Get user's posts"),
openapi.WithTags("users", "posts"),
openapi.WithResponse(200, []Post{}),
)
Security Requirements
Apply security to operations:
// Single security scheme
openapi.GET("/users/:id",
openapi.WithSummary("Get user"),
openapi.WithSecurity("bearerAuth"),
openapi.WithResponse(200, User{}),
)
// OAuth2 with scopes
openapi.POST("/users",
openapi.WithSummary("Create user"),
openapi.WithSecurity("oauth2", "read", "write"),
openapi.WithRequest(CreateUserRequest{}),
openapi.WithResponse(201, User{}),
)
// Multiple security schemes (OR)
openapi.DELETE("/users/:id",
openapi.WithSummary("Delete user"),
openapi.WithSecurity("bearerAuth"),
openapi.WithSecurity("apiKey"),
openapi.WithResponse(204, nil),
)
Deprecated Operations
Mark operations as deprecated:
openapi.GET("/users/legacy",
openapi.WithSummary("Legacy user list"),
openapi.WithDescription("This endpoint is deprecated. Use /users instead."),
openapi.WithDeprecated(),
openapi.WithResponse(200, []User{}),
)
Content Types
Specify content types for requests and responses:
openapi.POST("/users",
openapi.WithSummary("Create user"),
openapi.WithRequest(CreateUserRequest{}),
openapi.WithConsumes("application/json", "application/xml"),
openapi.WithProduces("application/json", "application/xml"),
openapi.WithResponse(201, User{}),
)
Operation Extensions
Add custom x-* extensions to operations:
openapi.GET("/users",
openapi.WithSummary("List users"),
openapi.WithOperationExtension("x-rate-limit", 100),
openapi.WithOperationExtension("x-internal-only", false),
openapi.WithResponse(200, []User{}),
)
Complete Operation Example
Here’s a complete example with all options:
openapi.PUT("/users/:id",
openapi.WithSummary("Update user"),
openapi.WithDescription("Updates an existing user's information"),
openapi.WithOperationID("updateUser"),
openapi.WithRequest(UpdateUserRequest{}),
openapi.WithResponse(200, User{}),
openapi.WithResponse(400, ErrorResponse{}),
openapi.WithResponse(404, ErrorResponse{}),
openapi.WithResponse(500, ErrorResponse{}),
openapi.WithTags("users"),
openapi.WithSecurity("bearerAuth"),
openapi.WithConsumes("application/json"),
openapi.WithProduces("application/json"),
openapi.WithOperationExtension("x-rate-limit", 50),
)
Composable Operation Options
Use WithOptions() to create reusable option sets:
// Define reusable option sets
var (
CommonErrors = openapi.WithOptions(
openapi.WithResponse(400, ErrorResponse{}),
openapi.WithResponse(401, ErrorResponse{}),
openapi.WithResponse(500, ErrorResponse{}),
)
UserEndpoint = openapi.WithOptions(
openapi.WithTags("users"),
openapi.WithSecurity("bearerAuth"),
CommonErrors,
)
JSONContent = openapi.WithOptions(
openapi.WithConsumes("application/json"),
openapi.WithProduces("application/json"),
)
)
// Apply to operations
result, err := api.Generate(context.Background(),
openapi.GET("/users/:id",
UserEndpoint,
JSONContent,
openapi.WithSummary("Get user"),
openapi.WithResponse(200, User{}),
),
openapi.POST("/users",
UserEndpoint,
JSONContent,
openapi.WithSummary("Create user"),
openapi.WithRequest(CreateUserRequest{}),
openapi.WithResponse(201, User{}),
),
openapi.PUT("/users/:id",
UserEndpoint,
JSONContent,
openapi.WithSummary("Update user"),
openapi.WithRequest(UpdateUserRequest{}),
openapi.WithResponse(200, User{}),
),
)
Nested Composable Options
Option sets can be nested:
var (
ErrorResponses = openapi.WithOptions(
openapi.WithResponse(400, ErrorResponse{}),
openapi.WithResponse(500, ErrorResponse{}),
)
AuthRequired = openapi.WithOptions(
openapi.WithSecurity("bearerAuth"),
openapi.WithResponse(401, ErrorResponse{}),
ErrorResponses,
)
UserAPI = openapi.WithOptions(
openapi.WithTags("users"),
AuthRequired,
)
)
Custom Operation IDs
By default, operation IDs are auto-generated from the HTTP method and path. Override with WithOperationID():
openapi.GET("/users/:id",
openapi.WithOperationID("getUserById"),
openapi.WithSummary("Get user"),
openapi.WithResponse(200, User{}),
)
Without WithOperationID(), the operation ID would be auto-generated as getUsers_id.
Next Steps
6 - Auto-Discovery
Use struct tags for automatic parameter discovery
Learn how the package automatically discovers API parameters from struct tags.
Overview
The package automatically discovers parameters from struct tags. This eliminates the need to manually define parameters in the OpenAPI specification.
Supported Parameter Types
The package supports four parameter locations:
path - Path parameters. Always required.query - Query parameters.header - Header parameters.cookie - Cookie parameters.
Basic Parameter Discovery
Define parameters using struct tags:
type GetUserRequest struct {
ID int `path:"id" doc:"User ID" example:"123"`
}
result, err := api.Generate(context.Background(),
openapi.GET("/users/:id",
openapi.WithSummary("Get user"),
openapi.WithResponse(200, User{}),
),
)
The package automatically discovers the id path parameter from the struct tag.
Path Parameters
Path parameters are always required and are extracted from the URL path:
type GetUserPostRequest struct {
UserID int `path:"user_id" doc:"User ID" example:"123"`
PostID int `path:"post_id" doc:"Post ID" example:"456"`
}
openapi.GET("/users/:user_id/posts/:post_id",
openapi.WithSummary("Get user's post"),
openapi.WithResponse(200, Post{}),
)
Query Parameters
Query parameters are extracted from the URL query string:
type ListUsersRequest struct {
Page int `query:"page" doc:"Page number" example:"1" validate:"min=1"`
PerPage int `query:"per_page" doc:"Items per page" example:"20" validate:"min=1,max=100"`
Sort string `query:"sort" doc:"Sort field" enum:"name,created_at"`
Tags []string `query:"tags" doc:"Filter by tags"`
Verified *bool `query:"verified" doc:"Filter by verification status"`
}
openapi.GET("/users",
openapi.WithSummary("List users"),
openapi.WithResponse(200, []User{}),
)
Header parameters are extracted from HTTP headers:
type GetUserRequest struct {
ID int `path:"id"`
Accept string `header:"Accept" doc:"Content type" enum:"application/json,application/xml"`
IfNoneMatch string `header:"If-None-Match" doc:"ETag for caching"`
XRequestID string `header:"X-Request-ID" doc:"Request correlation ID"`
}
openapi.GET("/users/:id",
openapi.WithSummary("Get user"),
openapi.WithResponse(200, User{}),
)
Cookie Parameters
Cookie parameters are extracted from HTTP cookies:
type GetUserRequest struct {
ID int `path:"id"`
SessionID string `cookie:"session_id" doc:"Session identifier"`
Theme string `cookie:"theme" doc:"UI theme preference" enum:"light,dark"`
}
openapi.GET("/users/:id",
openapi.WithSummary("Get user"),
openapi.WithResponse(200, User{}),
)
Request Body Fields
Fields in the request body use the json tag:
type CreateUserRequest struct {
Name string `json:"name" doc:"User's full name" example:"John Doe" validate:"required"`
Email string `json:"email" doc:"User's email address" example:"john@example.com" validate:"required,email"`
Age *int `json:"age,omitempty" doc:"User's age" example:"30" validate:"min=0,max=150"`
}
openapi.POST("/users",
openapi.WithSummary("Create user"),
openapi.WithRequest(CreateUserRequest{}),
openapi.WithResponse(201, User{}),
)
Use these tags to enhance parameter documentation:
doc Tag
Add descriptions to parameters:
type ListUsersRequest struct {
Page int `query:"page" doc:"Page number for pagination, starting at 1"`
}
example Tag
Provide example values:
type GetUserRequest struct {
ID int `path:"id" doc:"User ID" example:"123"`
}
enum Tag
Specify allowed values (comma-separated):
type ListUsersRequest struct {
Sort string `query:"sort" doc:"Sort field" enum:"name,email,created_at"`
Format string `query:"format" doc:"Response format" enum:"json,xml"`
}
validate Tag
Mark parameters as required or add validation constraints:
type CreateUserRequest struct {
Name string `json:"name" validate:"required"`
Email string `json:"email" validate:"required,email"`
Age int `json:"age" validate:"min=0,max=150"`
}
The required validation affects the required field in the OpenAPI spec.
Complete Struct Tag Example
Here’s a comprehensive example using all tag types:
type CreateOrderRequest struct {
// Path parameter (always required)
UserID int `path:"user_id" doc:"User ID" example:"123"`
// Query parameters
Coupon string `query:"coupon" doc:"Coupon code for discount" example:"SAVE20"`
SendEmail *bool `query:"send_email" doc:"Send confirmation email" example:"true"`
// Header parameters
IdempotencyKey string `header:"Idempotency-Key" doc:"Idempotency key for request" example:"550e8400-e29b-41d4-a716-446655440000"`
// Cookie parameters
SessionID string `cookie:"session_id" doc:"Session identifier"`
// Request body fields
Items []OrderItem `json:"items" doc:"Order items" validate:"required,min=1"`
Total float64 `json:"total" doc:"Order total" example:"99.99" validate:"required,min=0"`
Notes string `json:"notes,omitempty" doc:"Additional notes" example:"Please gift wrap"`
}
type OrderItem struct {
ProductID int `json:"product_id" validate:"required"`
Quantity int `json:"quantity" validate:"required,min=1"`
Price float64 `json:"price" validate:"required,min=0"`
}
Parameter Discovery Rules
Required vs Optional
- Path parameters: Always required
- Query/Header/Cookie parameters:
- Required if
validate:"required" tag is present - Optional otherwise
- Request body fields:
- Required if
validate:"required" tag is present - Optional if pointer type or
omitempty JSON tag
Type Conversion
The package automatically converts Go types to OpenAPI types:
type Parameters struct {
// String types
Name string `query:"name"` // type: string
// Integer types
Count int `query:"count"` // type: integer, format: int32
BigNum int64 `query:"big"` // type: integer, format: int64
// Floating-point types
Price float64 `query:"price"` // type: number, format: double
Rate float32 `query:"rate"` // type: number, format: float
// Boolean types
Active bool `query:"active"` // type: boolean
// Array types
Tags []string `query:"tags"` // type: array, items: string
IDs []int `query:"ids"` // type: array, items: integer
// Pointer types (optional)
Size *int `query:"size"` // type: integer, optional
}
Combining Parameters with Request Bodies
A single struct can contain both parameters and request body fields:
type UpdateUserRequest struct {
// Path parameter
ID int `path:"id" doc:"User ID"`
// Query parameter
Notify bool `query:"notify" doc:"Send notification"`
// Request body fields
Name string `json:"name" validate:"required"`
Email string `json:"email" validate:"required,email"`
}
openapi.PUT("/users/:id",
openapi.WithSummary("Update user"),
openapi.WithRequest(UpdateUserRequest{}),
openapi.WithResponse(200, User{}),
)
Nested Structures
Parameters can be in nested structures:
type GetUserRequest struct {
ID int `path:"id"`
Filter UserListFilter `query:",inline"`
}
type UserListFilter struct {
Active *bool `query:"active" doc:"Filter by active status"`
Role string `query:"role" doc:"Filter by role" enum:"admin,user,guest"`
Since string `query:"since" doc:"Filter by creation date"`
}
Next Steps
7 - Schema Generation
Understand how Go types are converted to OpenAPI schemas
Learn how the package automatically converts Go types to OpenAPI schemas.
Overview
The package uses reflection to convert Go types into OpenAPI schemas. This eliminates the need to manually define schemas in the specification.
Supported Go Types
Primitive Types
| Go Type | OpenAPI Type | OpenAPI Format |
|---|
string | string | - |
bool | boolean | - |
int, int32 | integer | int32 |
int64 | integer | int64 |
uint, uint32 | integer | int32 |
uint64 | integer | int64 |
float32 | number | float |
float64 | number | double |
byte | string | byte |
String Types
type User struct {
Name string `json:"name"`
Email string `json:"email"`
}
Generates:
type: object
properties:
name:
type: string
email:
type: string
Integer Types
type Product struct {
ID int `json:"id"`
Quantity int32 `json:"quantity"`
Stock int64 `json:"stock"`
}
Generates:
type: object
properties:
id:
type: integer
format: int32
quantity:
type: integer
format: int32
stock:
type: integer
format: int64
Floating-Point Types
type Product struct {
Price float64 `json:"price"`
Weight float32 `json:"weight"`
}
Generates:
type: object
properties:
price:
type: number
format: double
weight:
type: number
format: float
Boolean Types
type User struct {
Active bool `json:"active"`
Verified bool `json:"verified"`
}
Generates:
type: object
properties:
active:
type: boolean
verified:
type: boolean
Pointer Types
Pointer types are nullable and optional:
type User struct {
Name string `json:"name"`
Age *int `json:"age,omitempty"`
Email *string `json:"email,omitempty"`
}
In OpenAPI 3.1.x, pointers generate nullable: true. In OpenAPI 3.0.x, they’re optional fields.
Slices and Arrays
Slices become OpenAPI arrays:
type User struct {
Tags []string `json:"tags"`
Scores []int `json:"scores"`
Posts []Post `json:"posts"`
}
Generates:
type: object
properties:
tags:
type: array
items:
type: string
scores:
type: array
items:
type: integer
posts:
type: array
items:
$ref: '#/components/schemas/Post'
Maps
Maps become OpenAPI objects with additionalProperties:
type User struct {
Metadata map[string]string `json:"metadata"`
Scores map[string]int `json:"scores"`
}
Generates:
type: object
properties:
metadata:
type: object
additionalProperties:
type: string
scores:
type: object
additionalProperties:
type: integer
Nested Structs
Nested structs are converted to nested schemas or references:
type User struct {
ID int `json:"id"`
Name string `json:"name"`
Address Address `json:"address"`
}
type Address struct {
Street string `json:"street"`
City string `json:"city"`
ZipCode string `json:"zip_code"`
}
Generates component schemas with references:
components:
schemas:
User:
type: object
properties:
id:
type: integer
name:
type: string
address:
$ref: '#/components/schemas/Address'
Address:
type: object
properties:
street:
type: string
city:
type: string
zip_code:
type: string
Embedded Structs
Embedded struct fields are flattened into the parent:
type Timestamps struct {
CreatedAt time.Time `json:"created_at"`
UpdatedAt time.Time `json:"updated_at"`
}
type User struct {
ID int `json:"id"`
Name string `json:"name"`
Timestamps
}
Generates:
type: object
properties:
id:
type: integer
name:
type: string
created_at:
type: string
format: date-time
updated_at:
type: string
format: date-time
Time Types
time.Time becomes a string with date-time format:
type User struct {
CreatedAt time.Time `json:"created_at"`
UpdatedAt *time.Time `json:"updated_at,omitempty"`
}
Generates:
type: object
properties:
created_at:
type: string
format: date-time
updated_at:
type: string
format: date-time
The package respects json struct tags:
type User struct {
ID int `json:"id"`
FirstName string `json:"first_name"`
LastName string `json:"last_name"`
Internal string `json:"-"` // Ignored
Optional string `json:"opt,omitempty"` // Optional
}
json:"name" - Sets the property namejson:"-" - Field is ignoredjson:",omitempty" - Field is optional (not required)
Schema Naming
Component schema names use the format pkgname.TypeName to prevent collisions:
// In package "api"
type User struct { ... } // Becomes "api.User"
// In package "models"
type User struct { ... } // Becomes "models.User"
This prevents naming collisions when the same type name exists in different packages.
Custom Schema Names
If you need custom schema names, use the openapi struct tag:
type User struct {
ID int `json:"id" openapi:"name=CustomUser"`
Name string `json:"name"`
}
Validation tags affect the OpenAPI schema:
type CreateUserRequest struct {
Name string `json:"name" validate:"required"`
Email string `json:"email" validate:"required,email"`
Age int `json:"age" validate:"min=0,max=150"`
}
required - Adds field to required arraymin/max - Sets minimum/maximum for numbersemail - Sets format: email for strings
Use doc and example tags to enhance schemas:
type User struct {
ID int `json:"id" doc:"Unique user identifier" example:"123"`
Name string `json:"name" doc:"User's full name" example:"John Doe"`
Email string `json:"email" doc:"User's email address" example:"john@example.com"`
}
Generates:
type: object
properties:
id:
type: integer
description: Unique user identifier
example: 123
name:
type: string
description: User's full name
example: John Doe
email:
type: string
description: User's email address
example: john@example.com
Enum Types
Use enum tag to specify allowed values:
type User struct {
Role string `json:"role" enum:"admin,user,guest"`
Status string `json:"status" enum:"active,inactive,pending"`
}
Generates:
type: object
properties:
role:
type: string
enum: [admin, user, guest]
status:
type: string
enum: [active, inactive, pending]
Complete Schema Example
Here’s a comprehensive example using all features:
package main
import (
"time"
)
type User struct {
// Basic types
ID int `json:"id" doc:"Unique user identifier" example:"123"`
Name string `json:"name" doc:"User's full name" example:"John Doe" validate:"required"`
Email string `json:"email" doc:"Email address" example:"john@example.com" validate:"required,email"`
// Optional field
Bio *string `json:"bio,omitempty" doc:"User biography"`
// Numeric types
Age int `json:"age" doc:"User's age" validate:"min=0,max=150"`
Score float64 `json:"score" doc:"User score" example:"95.5"`
// Boolean
Active bool `json:"active" doc:"Whether user is active" example:"true"`
// Enum
Role string `json:"role" doc:"User role" enum:"admin,user,guest"`
// Arrays
Tags []string `json:"tags" doc:"User tags"`
Scores []int `json:"scores" doc:"Test scores"`
// Map
Metadata map[string]string `json:"metadata" doc:"Additional metadata"`
// Nested struct
Address Address `json:"address" doc:"User address"`
// Time
CreatedAt time.Time `json:"created_at" doc:"Creation timestamp"`
UpdatedAt *time.Time `json:"updated_at,omitempty" doc:"Last update timestamp"`
// Ignored field
Internal string `json:"-"`
}
type Address struct {
Street string `json:"street" validate:"required"`
City string `json:"city" validate:"required"`
State string `json:"state"`
ZipCode string `json:"zip_code" validate:"required"`
Country string `json:"country" validate:"required"`
}
Next Steps
- Learn about Operations to use your schemas in API endpoints
- Explore Validation to validate generated specifications
- See Examples for complete schema patterns
8 - Swagger UI
Customize the Swagger UI interface for API documentation
Learn how to configure and customize the Swagger UI interface for your OpenAPI specification.
Overview
The package includes built-in Swagger UI support with extensive customization options. Swagger UI provides an interactive interface for exploring and testing your API.
Basic Configuration
Enable Swagger UI by specifying the path:
api := openapi.MustNew(
openapi.WithTitle("My API", "1.0.0"),
openapi.WithSwaggerUI("/docs"),
)
This serves Swagger UI at /docs with default settings.
Disabling Swagger UI
To disable Swagger UI completely:
api := openapi.MustNew(
openapi.WithTitle("My API", "1.0.0"),
openapi.WithoutSwaggerUI(),
)
Display Options
Document Expansion
Control how documentation is initially displayed:
openapi.WithSwaggerUI("/docs",
openapi.WithUIExpansion(openapi.DocExpansionList),
)
Available options:
DocExpansionList - Show endpoints, hide details. This is the default.DocExpansionFull - Show endpoints and details.DocExpansionNone - Hide everything.
Model Rendering
Control how models/schemas are rendered:
openapi.WithSwaggerUI("/docs",
openapi.WithUIDefaultModelRendering(openapi.ModelRenderingExample),
)
Options:
ModelRenderingExample - Show example values. This is the default.ModelRenderingModel - Show schema structure.
Model Expand Depth
Control how deeply nested models are expanded:
openapi.WithSwaggerUI("/docs",
openapi.WithUIModelExpandDepth(1), // How deep to expand a single model
openapi.WithUIModelsExpandDepth(1), // How deep to expand models section
)
Set to -1 to disable expansion, 1 for shallow, higher numbers for deeper.
Display Operation IDs
Show operation IDs alongside summaries:
openapi.WithSwaggerUI("/docs",
openapi.WithUIDisplayOperationID(true),
)
Try It Out Features
Enable Try It Out
Allow users to test API endpoints directly:
openapi.WithSwaggerUI("/docs",
openapi.WithUITryItOut(true),
)
Request Snippets
Show code snippets for making requests:
openapi.WithSwaggerUI("/docs",
openapi.WithUIRequestSnippets(true,
openapi.SnippetCurlBash,
openapi.SnippetCurlPowerShell,
openapi.SnippetCurlCmd,
),
)
Available snippet types:
SnippetCurlBash - curl for bash/sh shellsSnippetCurlPowerShell - curl for PowerShellSnippetCurlCmd - curl for Windows CMD
Request Snippets Expanded
Expand request snippets by default:
openapi.WithSwaggerUI("/docs",
openapi.WithUIRequestSnippets(true, openapi.SnippetCurlBash),
openapi.WithUIRequestSnippetsExpanded(true),
)
Display Request Duration
Show how long requests take:
openapi.WithSwaggerUI("/docs",
openapi.WithUIDisplayRequestDuration(true),
)
Filtering and Sorting
Filter
Enable a filter/search box:
openapi.WithSwaggerUI("/docs",
openapi.WithUIFilter(true),
)
Limit the number of tags displayed:
openapi.WithSwaggerUI("/docs",
openapi.WithUIMaxDisplayedTags(10),
)
Operations Sorting
Sort operations alphabetically or by HTTP method:
openapi.WithSwaggerUI("/docs",
openapi.WithUIOperationsSorter(openapi.OperationsSorterAlpha),
)
Options:
OperationsSorterAlpha - Sort alphabeticallyOperationsSorterMethod - Sort by HTTP method- Leave unset for default order
Sort tags alphabetically:
openapi.WithSwaggerUI("/docs",
openapi.WithUITagsSorter(openapi.TagsSorterAlpha),
)
Syntax Highlighting
Enable/Disable Syntax Highlighting
openapi.WithSwaggerUI("/docs",
openapi.WithUISyntaxHighlight(true),
)
Syntax Theme
Choose a color theme for code highlighting:
openapi.WithSwaggerUI("/docs",
openapi.WithUISyntaxTheme(openapi.SyntaxThemeMonokai),
)
Available themes:
SyntaxThemeAgate - Dark theme with blue accentsSyntaxThemeArta - Dark theme with orange accentsSyntaxThemeMonokai - Dark theme with vibrant colorsSyntaxThemeNord - Dark theme with cool blue tonesSyntaxThemeObsidian - Dark theme with green accentsSyntaxThemeTomorrowNight - Dark theme with muted colorsSyntaxThemeIdea - Light theme similar to IntelliJ IDEA
Authentication
Persist Authentication
Keep auth credentials across browser refreshes:
openapi.WithSwaggerUI("/docs",
openapi.WithUIPersistAuth(true),
)
Send Credentials
Include credentials in requests:
openapi.WithSwaggerUI("/docs",
openapi.WithUIWithCredentials(true),
)
Validation
Control OpenAPI specification validation:
// Use local validation (recommended)
openapi.WithSwaggerUI("/docs",
openapi.WithUIValidator(openapi.ValidatorLocal),
)
// Use external validator
openapi.WithSwaggerUI("/docs",
openapi.WithUIValidator("https://validator.swagger.io/validator"),
)
// Disable validation
openapi.WithSwaggerUI("/docs",
openapi.WithUIValidator(openapi.ValidatorNone),
)
Complete Swagger UI Example
Here’s a comprehensive example with all common options:
package main
import (
"rivaas.dev/openapi"
)
func main() {
api := openapi.MustNew(
openapi.WithTitle("My API", "1.0.0"),
openapi.WithSwaggerUI("/docs",
// Document expansion
openapi.WithUIExpansion(openapi.DocExpansionList),
openapi.WithUIModelsExpandDepth(1),
openapi.WithUIModelExpandDepth(1),
// Display options
openapi.WithUIDisplayOperationID(true),
openapi.WithUIDefaultModelRendering(openapi.ModelRenderingExample),
// Try it out
openapi.WithUITryItOut(true),
openapi.WithUIRequestSnippets(true,
openapi.SnippetCurlBash,
openapi.SnippetCurlPowerShell,
openapi.SnippetCurlCmd,
),
openapi.WithUIRequestSnippetsExpanded(true),
openapi.WithUIDisplayRequestDuration(true),
// Filtering and sorting
openapi.WithUIFilter(true),
openapi.WithUIMaxDisplayedTags(10),
openapi.WithUIOperationsSorter(openapi.OperationsSorterAlpha),
openapi.WithUITagsSorter(openapi.TagsSorterAlpha),
// Syntax highlighting
openapi.WithUISyntaxHighlight(true),
openapi.WithUISyntaxTheme(openapi.SyntaxThemeMonokai),
// Authentication
openapi.WithUIPersistAuth(true),
openapi.WithUIWithCredentials(true),
// Validation
openapi.WithUIValidator(openapi.ValidatorLocal),
),
)
// Generate specification...
}
Swagger UI Path
The Swagger UI path can be any valid URL path:
openapi.WithSwaggerUI("/api-docs")
openapi.WithSwaggerUI("/swagger")
openapi.WithSwaggerUI("/docs/api")
Integration with Web Frameworks
The package generates the OpenAPI specification, but you need to integrate it with your web framework to serve Swagger UI. The typical pattern is:
// Generate the spec
result, err := api.Generate(context.Background(), operations...)
if err != nil {
log.Fatal(err)
}
// Serve the spec at /openapi.json
http.HandleFunc("/openapi.json", func(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "application/json")
w.Write(result.JSON)
})
// Serve Swagger UI at /docs
// (Framework-specific implementation)
Next Steps
9 - Validation
Validate OpenAPI specifications against official meta-schemas
Learn how to validate OpenAPI specifications using built-in validation against official meta-schemas.
Overview
The package provides built-in validation against official OpenAPI meta-schemas for both 3.0.x and 3.1.x specifications.
Enabling Validation
Validation is disabled by default for performance. Enable it during development or in CI/CD pipelines:
api := openapi.MustNew(
openapi.WithTitle("My API", "1.0.0"),
openapi.WithValidation(true), // Enable validation
)
result, err := api.Generate(context.Background(), operations...)
if err != nil {
log.Fatal(err) // Will fail if spec is invalid
}
Why Validation is Disabled by Default
Validation has a performance cost:
- Schema compilation on first use.
- JSON schema validation for every generation.
- Not necessary for production spec generation.
When to enable:
- During development.
- In CI/CD pipelines.
- When debugging specification issues.
- When accepting external specifications.
When to disable:
- Production spec generation.
- When performance is critical.
- After spec validation is confirmed.
Validation Errors
When validation fails, you’ll receive a detailed error:
result, err := api.Generate(context.Background(), operations...)
if err != nil {
// Error contains validation details
fmt.Printf("Validation failed: %v\n", err)
}
Common validation errors:
- Missing required fields like
info, openapi, paths. - Invalid field types.
- Invalid format values.
- Schema constraint violations.
- Invalid references.
Validating External Specifications
The package includes a standalone validator for external OpenAPI specifications:
import "rivaas.dev/openapi/validate"
// Read external spec
specJSON, err := os.ReadFile("external-api.json")
if err != nil {
log.Fatal(err)
}
// Create validator
validator := validate.New()
// Validate against OpenAPI 3.0.x
err = validator.Validate(context.Background(), specJSON, validate.V30)
if err != nil {
log.Printf("Validation failed: %v\n", err)
}
// Or validate against OpenAPI 3.1.x
err = validator.Validate(context.Background(), specJSON, validate.V31)
if err != nil {
log.Printf("Validation failed: %v\n", err)
}
Auto-Detection
The validator can auto-detect the OpenAPI version:
validator := validate.New()
// Auto-detects version from the spec
err := validator.ValidateAuto(context.Background(), specJSON)
if err != nil {
log.Printf("Validation failed: %v\n", err)
}
Swagger UI Validation
Configure validation in Swagger UI:
Local Validation (Recommended)
Use the built-in validator (no external calls):
api := openapi.MustNew(
openapi.WithTitle("My API", "1.0.0"),
openapi.WithSwaggerUI("/docs",
openapi.WithUIValidator(openapi.ValidatorLocal),
),
)
External Validator
Use an external validation service:
api := openapi.MustNew(
openapi.WithTitle("My API", "1.0.0"),
openapi.WithSwaggerUI("/docs",
openapi.WithUIValidator("https://validator.swagger.io/validator"),
),
)
Disable Validation
Disable validation in Swagger UI:
api := openapi.MustNew(
openapi.WithTitle("My API", "1.0.0"),
openapi.WithSwaggerUI("/docs",
openapi.WithUIValidator(openapi.ValidatorNone),
),
)
Validation in CI/CD
Add validation to your CI/CD pipeline:
# generate-openapi.go
package main
import (
"context"
"log"
"os"
"rivaas.dev/openapi"
)
func main() {
api := openapi.MustNew(
openapi.WithTitle("My API", "1.0.0"),
openapi.WithValidation(true), // Enable for CI/CD
)
result, err := api.Generate(context.Background(),
// ... operations
)
if err != nil {
log.Fatalf("Validation failed: %v", err)
}
// Write to file
if err := os.WriteFile("openapi.json", result.JSON, 0644); err != nil {
log.Fatal(err)
}
log.Println("Valid OpenAPI specification generated")
}
In your CI pipeline:
# .github/workflows/validate-openapi.yml
name: Validate OpenAPI
on: [push, pull_request]
jobs:
validate:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: actions/setup-go@v4
with:
go-version: '1.25'
- run: go run generate-openapi.go
Validation performance characteristics:
- First validation: ~10-20ms (schema compilation)
- Subsequent validations: ~1-5ms (using cached schema)
- External spec validation: Depends on spec size
For high-performance scenarios, consider:
- Validate once during build/deployment
- Cache validated specifications
- Disable validation in production spec generation
Complete Validation Example
package main
import (
"context"
"fmt"
"log"
"os"
"rivaas.dev/openapi"
"rivaas.dev/openapi/validate"
)
type User struct {
ID int `json:"id"`
Name string `json:"name"`
}
func main() {
// Generate with validation enabled
api := openapi.MustNew(
openapi.WithTitle("User API", "1.0.0"),
openapi.WithValidation(true),
)
result, err := api.Generate(context.Background(),
openapi.GET("/users/:id",
openapi.WithSummary("Get user"),
openapi.WithResponse(200, User{}),
),
)
if err != nil {
log.Fatalf("Generation/validation failed: %v", err)
}
fmt.Println("Generated valid OpenAPI 3.0.4 specification")
// Write to file
if err := os.WriteFile("openapi.json", result.JSON, 0644); err != nil {
log.Fatal(err)
}
// Validate external spec (e.g., from a file)
externalSpec, err := os.ReadFile("external-api.json")
if err != nil {
log.Fatal(err)
}
validator := validate.New()
if err := validator.ValidateAuto(context.Background(), externalSpec); err != nil {
log.Printf("External spec validation failed: %v\n", err)
} else {
fmt.Println("External spec is valid")
}
}
Validation vs Warnings
It’s important to distinguish between validation errors and warnings:
- Validation errors: The specification violates OpenAPI schema requirements
- Warnings: The specification is valid but uses version-specific features (see Diagnostics)
api := openapi.MustNew(
openapi.WithTitle("API", "1.0.0"),
openapi.WithVersion(openapi.V30x),
openapi.WithInfoSummary("Summary"), // 3.1-only feature
openapi.WithValidation(true),
)
result, err := api.Generate(context.Background(), ops...)
// err is nil (spec is valid)
// result.Warnings contains warning about info.summary being dropped
Next Steps
10 - Diagnostics
Handle warnings with type-safe diagnostics
Learn how to work with warnings using the type-safe diagnostics package.
Overview
The package generates warnings when using version-specific features. For example, using OpenAPI 3.1 features with a 3.0 target generates warnings instead of errors.
Working with Warnings
Check for warnings in the generation result:
result, err := api.Generate(context.Background(), operations...)
if err != nil {
log.Fatal(err)
}
// Basic warning check
if len(result.Warnings) > 0 {
fmt.Printf("Generated with %d warnings\n", len(result.Warnings))
}
// Iterate through warnings
for _, warn := range result.Warnings {
fmt.Printf("[%s] %s\n", warn.Code(), warn.Message())
}
The diag Package
Import the diag package for type-safe warning handling:
import "rivaas.dev/openapi/diag"
Warning Interface
Each warning implements the Warning interface:
type Warning interface {
Code() WarningCode // Unique warning code
Message() string // Human-readable message
Path() string // Location in spec (e.g., "info.summary")
Category() WarningCategory // Warning category
}
Type-Safe Warning Checks
Check for specific warnings using type-safe constants:
import "rivaas.dev/openapi/diag"
result, err := api.Generate(context.Background(), ops...)
if err != nil {
log.Fatal(err)
}
// Check for specific warning
if result.Warnings.Has(diag.WarnDownlevelWebhooks) {
log.Warn("webhooks not supported in OpenAPI 3.0")
}
// Check for any of multiple codes
if result.Warnings.HasAny(
diag.WarnDownlevelMutualTLS,
diag.WarnDownlevelWebhooks,
) {
log.Warn("Some 3.1 security features were dropped")
}
Warning Categories
Warnings are organized into categories:
// Filter by category
downlevelWarnings := result.Warnings.FilterCategory(diag.CategoryDownlevel)
fmt.Printf("Downlevel warnings: %d\n", len(downlevelWarnings))
deprecationWarnings := result.Warnings.FilterCategory(diag.CategoryDeprecation)
fmt.Printf("Deprecation warnings: %d\n", len(deprecationWarnings))
Available categories:
CategoryDownlevel - 3.1 to 3.0 conversion feature lossesCategoryDeprecation - Deprecated feature usage warningsCategoryUnknown - Unrecognized warning codes
Warning Codes
Common warning codes:
Downlevel Warnings
These occur when using 3.1 features with a 3.0 target:
WarnDownlevelWebhooks - Webhooks droppedWarnDownlevelInfoSummary - info.summary droppedWarnDownlevelLicenseIdentifier - license.identifier droppedWarnDownlevelMutualTLS - mutualTLS security scheme droppedWarnDownlevelConstToEnum - JSON Schema const converted to enumWarnDownlevelMultipleExamples - Multiple examples collapsed to oneWarnDownlevelPatternProperties - patternProperties droppedWarnDownlevelUnevaluatedProperties - unevaluatedProperties droppedWarnDownlevelContentEncoding - contentEncoding droppedWarnDownlevelContentMediaType - contentMediaType dropped
Deprecation Warnings
These occur when using deprecated features:
WarnDeprecationExampleSingular - Using deprecated singular example field
Filtering Warnings
Filter Specific Warnings
Get only specific warning types:
licenseWarnings := result.Warnings.Filter(diag.WarnDownlevelLicenseIdentifier)
for _, warn := range licenseWarnings {
fmt.Printf("%s: %s\n", warn.Path(), warn.Message())
}
Exclude Expected Warnings
Exclude warnings you expect and want to ignore:
unexpected := result.Warnings.Exclude(
diag.WarnDownlevelInfoSummary,
diag.WarnDownlevelLicenseIdentifier,
)
if len(unexpected) > 0 {
fmt.Printf("Unexpected warnings: %d\n", len(unexpected))
for _, warn := range unexpected {
fmt.Printf("[%s] %s\n", warn.Code(), warn.Message())
}
}
Complete Diagnostics Example
package main
import (
"context"
"fmt"
"log"
"rivaas.dev/openapi"
"rivaas.dev/openapi/diag"
)
type User struct {
ID int `json:"id"`
Name string `json:"name"`
}
func main() {
// Create API with 3.0 target but use 3.1 features
api := openapi.MustNew(
openapi.WithTitle("My API", "1.0.0"),
openapi.WithVersion(openapi.V30x),
openapi.WithInfoSummary("Short summary"), // 3.1-only feature
)
result, err := api.Generate(context.Background(),
openapi.GET("/users/:id",
openapi.WithSummary("Get user"),
openapi.WithResponse(200, User{}),
),
)
if err != nil {
log.Fatal(err)
}
// Check for specific warning
if result.Warnings.Has(diag.WarnDownlevelInfoSummary) {
fmt.Println("info.summary was dropped (3.1 feature with 3.0 target)")
}
// Filter by category
downlevelWarnings := result.Warnings.FilterCategory(diag.CategoryDownlevel)
if len(downlevelWarnings) > 0 {
fmt.Printf("\nDownlevel warnings (%d):\n", len(downlevelWarnings))
for _, warn := range downlevelWarnings {
fmt.Printf(" [%s] %s at %s\n",
warn.Code(),
warn.Message(),
warn.Path(),
)
}
}
// Check for unexpected warnings
expected := []diag.WarningCode{
diag.WarnDownlevelInfoSummary,
}
unexpected := result.Warnings.Exclude(expected...)
if len(unexpected) > 0 {
fmt.Printf("\nUnexpected warnings (%d):\n", len(unexpected))
for _, warn := range unexpected {
fmt.Printf(" [%s] %s\n", warn.Code(), warn.Message())
}
}
fmt.Printf("\nGenerated %d byte specification with %d warnings\n",
len(result.JSON),
len(result.Warnings),
)
}
Warning vs Error
The package distinguishes between warnings and errors:
- Warnings: The specification is valid but features were dropped or converted
- Errors: The specification is invalid or generation failed
result, err := api.Generate(context.Background(), ops...)
if err != nil {
// Hard error - generation failed
log.Fatal(err)
}
if len(result.Warnings) > 0 {
// Soft warnings - generation succeeded with caveats
for _, warn := range result.Warnings {
log.Printf("Warning: %s\n", warn.Message())
}
}
Strict Downlevel Mode
To treat downlevel warnings as errors, enable strict mode (see Advanced Usage):
api := openapi.MustNew(
openapi.WithTitle("API", "1.0.0"),
openapi.WithVersion(openapi.V30x),
openapi.WithStrictDownlevel(true), // Error on 3.1 features
openapi.WithInfoSummary("Summary"), // This will cause an error
)
_, err := api.Generate(context.Background(), ops...)
// err will be non-nil due to strict mode violation
Warning Suppression
Currently, the package does not support per-warning suppression. To handle expected warnings:
- Filter them out after generation
- Use strict mode to error on any warnings
- Log and ignore specific warning codes
// Filter out expected warnings
expected := []diag.WarningCode{
diag.WarnDownlevelInfoSummary,
diag.WarnDownlevelLicenseIdentifier,
}
unexpected := result.Warnings.Exclude(expected...)
if len(unexpected) > 0 {
log.Fatalf("Unexpected warnings: %d", len(unexpected))
}
Next Steps
11 - Advanced Usage
Custom operation IDs, extensions, and strict downlevel mode
Learn about advanced features including custom operation IDs, extensions, and strict downlevel mode.
Custom Operation IDs
By default, operation IDs are auto-generated from the HTTP method and path. You can override this behavior.
Auto-Generated Operation IDs
openapi.GET("/users/:id",
openapi.WithSummary("Get user"),
openapi.WithResponse(200, User{}),
)
// Generated operation ID: "getUsers_id"
openapi.POST("/users",
openapi.WithSummary("Create user"),
openapi.WithRequest(CreateUserRequest{}),
openapi.WithResponse(201, User{}),
)
// Generated operation ID: "postUsers"
Custom Operation IDs
Override with WithOperationID():
openapi.GET("/users/:id",
openapi.WithOperationID("getUserById"),
openapi.WithSummary("Get user"),
openapi.WithResponse(200, User{}),
)
openapi.POST("/users",
openapi.WithOperationID("createNewUser"),
openapi.WithSummary("Create user"),
openapi.WithRequest(CreateUserRequest{}),
openapi.WithResponse(201, User{}),
)
Operation ID Best Practices
- Use camelCase - Consistent with most API conventions.
- Be descriptive -
getUserById rather than getUser1. - Avoid conflicts - Ensure unique IDs across all operations.
- Consider generation - Some tools generate client code from operation IDs.
Extensions
OpenAPI allows custom x-* extensions for vendor-specific metadata.
Root-Level Extensions
Add extensions to the root of the specification:
api := openapi.MustNew(
openapi.WithTitle("My API", "1.0.0"),
openapi.WithExtension("x-api-version", "v2"),
openapi.WithExtension("x-custom-feature", true),
openapi.WithExtension("x-rate-limit-config", map[string]interface{}{
"requests": 100,
"period": "1m",
}),
)
Operation Extensions
Add extensions to specific operations.
openapi.GET("/users",
openapi.WithSummary("List users"),
openapi.WithOperationExtension("x-rate-limit", 100),
openapi.WithOperationExtension("x-cache-ttl", 300),
openapi.WithOperationExtension("x-internal-only", false),
openapi.WithResponse(200, []User{}),
)
Extension Naming Rules
- Must start with
x- - Required by OpenAPI specification - Reserved prefixes -
x-oai- and x-oas- are reserved in 3.1.x - Case-sensitive -
x-Custom and x-custom are different
Extension Validation
Extensions are validated:
// Valid
openapi.WithExtension("x-custom", "value")
// Invalid - doesn't start with x-
openapi.WithExtension("custom", "value") // Error
// Invalid - reserved prefix in 3.1.x
openapi.WithExtension("x-oai-custom", "value") // Filtered out in 3.1.x
Common Extension Use Cases
// API versioning
openapi.WithExtension("x-api-version", "2.0")
// Rate limiting
openapi.WithOperationExtension("x-rate-limit", map[string]interface{}{
"requests": 100,
"window": "1m",
})
// Caching
openapi.WithOperationExtension("x-cache", map[string]interface{}{
"ttl": 300,
"vary": []string{"Authorization", "Accept-Language"},
})
// Internal metadata
openapi.WithOperationExtension("x-internal", map[string]interface{}{
"team": "platform",
"cost": "low",
})
// Feature flags
openapi.WithOperationExtension("x-feature-flag", "new-user-flow")
// Code generation hints
openapi.WithOperationExtension("x-codegen", map[string]interface{}{
"methodName": "customMethodName",
"packageName": "users",
})
Strict Downlevel Mode
By default, using 3.1 features with a 3.0 target generates warnings. Enable strict mode to error instead:
Default Behavior (Warnings)
api := openapi.MustNew(
openapi.WithTitle("API", "1.0.0"),
openapi.WithVersion(openapi.V30x),
openapi.WithInfoSummary("Summary"), // 3.1-only feature
)
result, err := api.Generate(context.Background(), ops...)
// err is nil (generation succeeds)
// result.Warnings contains warning about info.summary being dropped
Strict Mode (Errors)
api := openapi.MustNew(
openapi.WithTitle("API", "1.0.0"),
openapi.WithVersion(openapi.V30x),
openapi.WithStrictDownlevel(true), // Enable strict mode
openapi.WithInfoSummary("Summary"), // This will cause an error
)
result, err := api.Generate(context.Background(), ops...)
// err is non-nil (generation fails)
When to Use Strict Mode
Use strict mode when:
- Enforcing version compliance - Prevent accidental 3.1 feature usage
- CI/CD validation - Fail builds on version violations
- Team standards - Ensure consistent OpenAPI version usage
- Client compatibility - Target clients require strict 3.0 compliance
Don’t use strict mode when:
- Graceful degradation - You’re okay with features being dropped
- Development - Exploring features without hard errors
- Flexible deployments - Different environments support different versions
Features Affected by Strict Mode
3.1-only features that trigger strict mode:
WithInfoSummary() - Short API summaryWithLicenseIdentifier() - SPDX license identifier- Webhooks - Webhook definitions
- Mutual TLS -
mutualTLS security scheme const in schemas - JSON Schema const keyword- Multiple
examples - Multiple schema examples
Complete Advanced Example
package main
import (
"context"
"fmt"
"log"
"rivaas.dev/openapi"
)
type User struct {
ID int `json:"id"`
Name string `json:"name"`
}
type CreateUserRequest struct {
Name string `json:"name" validate:"required"`
}
func main() {
api := openapi.MustNew(
// Basic configuration
openapi.WithTitle("Advanced API", "1.0.0"),
openapi.WithVersion(openapi.V30x),
// Root-level extensions
openapi.WithExtension("x-api-version", "v2"),
openapi.WithExtension("x-environment", "production"),
openapi.WithExtension("x-service-info", map[string]interface{}{
"team": "platform",
"repository": "github.com/example/api",
}),
// Strict mode (optional)
openapi.WithStrictDownlevel(false), // Allow graceful degradation
)
result, err := api.Generate(context.Background(),
// Custom operation IDs
openapi.GET("/users/:id",
openapi.WithOperationID("getUserById"),
openapi.WithSummary("Get user by ID"),
// Operation extensions
openapi.WithOperationExtension("x-rate-limit", 100),
openapi.WithOperationExtension("x-cache-ttl", 300),
openapi.WithOperationExtension("x-internal-team", "users"),
openapi.WithResponse(200, User{}),
),
openapi.POST("/users",
openapi.WithOperationID("createUser"),
openapi.WithSummary("Create a new user"),
// Different extensions per operation
openapi.WithOperationExtension("x-rate-limit", 10),
openapi.WithOperationExtension("x-feature-flag", "new-user-flow"),
openapi.WithOperationExtension("x-mutation", true),
openapi.WithRequest(CreateUserRequest{}),
openapi.WithResponse(201, User{}),
),
openapi.PUT("/users/:id",
openapi.WithOperationID("updateUser"),
openapi.WithSummary("Update user"),
openapi.WithOperationExtension("x-rate-limit", 50),
openapi.WithOperationExtension("x-mutation", true),
openapi.WithRequest(CreateUserRequest{}),
openapi.WithResponse(200, User{}),
),
openapi.DELETE("/users/:id",
openapi.WithOperationID("deleteUser"),
openapi.WithSummary("Delete user"),
openapi.WithOperationExtension("x-rate-limit", 10),
openapi.WithOperationExtension("x-mutation", true),
openapi.WithOperationExtension("x-dangerous", true),
openapi.WithResponse(204, nil),
),
)
if err != nil {
log.Fatal(err)
}
// Check for warnings
if len(result.Warnings) > 0 {
fmt.Printf("Generated with %d warnings:\n", len(result.Warnings))
for _, warn := range result.Warnings {
fmt.Printf(" - %s\n", warn.Message())
}
}
fmt.Printf("Generated %d byte specification\n", len(result.JSON))
}
Best Practices
Operation IDs
- Be consistent - Use the same naming convention across all operations
- Make them unique - Avoid duplicate operation IDs
- Consider clients - Generated client libraries use these names
- Document the convention - Help team members follow the pattern
Extensions
- Use sparingly - Only add extensions when necessary
- Document them - Explain what custom extensions mean
- Validate format - Ensure extensions follow your schema
- Version them - Consider versioning extension formats
- Tool compatibility - Check if tools support your extensions
Strict Mode
- Enable in CI/CD - Catch version issues early
- Document the choice - Explain why strict mode is enabled/disabled
- Test both modes - Ensure graceful degradation works if disabled
- Communicate clearly - Make version requirements explicit
Next Steps
12 - Examples
Complete examples and real-world usage patterns
Complete examples demonstrating real-world usage patterns for the OpenAPI package.
Basic CRUD API
A simple CRUD API with all HTTP methods.
package main
import (
"context"
"log"
"os"
"time"
"rivaas.dev/openapi"
)
type User struct {
ID int `json:"id" doc:"User ID" example:"123"`
Name string `json:"name" doc:"User's full name" example:"John Doe"`
Email string `json:"email" doc:"Email address" example:"john@example.com"`
CreatedAt time.Time `json:"created_at" doc:"Creation timestamp"`
}
type CreateUserRequest struct {
Name string `json:"name" doc:"User's full name" validate:"required"`
Email string `json:"email" doc:"Email address" validate:"required,email"`
}
type ErrorResponse struct {
Code int `json:"code" doc:"Error code"`
Message string `json:"message" doc:"Error message"`
}
func main() {
api := openapi.MustNew(
openapi.WithTitle("User API", "1.0.0"),
openapi.WithInfoDescription("Simple CRUD API for user management"),
openapi.WithServer("http://localhost:8080", "Local development"),
openapi.WithServer("https://api.example.com", "Production"),
openapi.WithBearerAuth("bearerAuth", "JWT authentication"),
openapi.WithTag("users", "User management operations"),
)
result, err := api.Generate(context.Background(),
openapi.GET("/users",
openapi.WithSummary("List users"),
openapi.WithDescription("Retrieve a list of all users"),
openapi.WithTags("users"),
openapi.WithSecurity("bearerAuth"),
openapi.WithResponse(200, []User{}),
openapi.WithResponse(401, ErrorResponse{}),
),
openapi.GET("/users/:id",
openapi.WithSummary("Get user"),
openapi.WithDescription("Retrieve a specific user by ID"),
openapi.WithTags("users"),
openapi.WithSecurity("bearerAuth"),
openapi.WithResponse(200, User{}),
openapi.WithResponse(404, ErrorResponse{}),
openapi.WithResponse(401, ErrorResponse{}),
),
openapi.POST("/users",
openapi.WithSummary("Create user"),
openapi.WithDescription("Create a new user"),
openapi.WithTags("users"),
openapi.WithSecurity("bearerAuth"),
openapi.WithRequest(CreateUserRequest{}),
openapi.WithResponse(201, User{}),
openapi.WithResponse(400, ErrorResponse{}),
openapi.WithResponse(401, ErrorResponse{}),
),
openapi.PUT("/users/:id",
openapi.WithSummary("Update user"),
openapi.WithDescription("Update an existing user"),
openapi.WithTags("users"),
openapi.WithSecurity("bearerAuth"),
openapi.WithRequest(CreateUserRequest{}),
openapi.WithResponse(200, User{}),
openapi.WithResponse(400, ErrorResponse{}),
openapi.WithResponse(404, ErrorResponse{}),
openapi.WithResponse(401, ErrorResponse{}),
),
openapi.DELETE("/users/:id",
openapi.WithSummary("Delete user"),
openapi.WithDescription("Delete a user"),
openapi.WithTags("users"),
openapi.WithSecurity("bearerAuth"),
openapi.WithResponse(204, nil),
openapi.WithResponse(404, ErrorResponse{}),
openapi.WithResponse(401, ErrorResponse{}),
),
)
if err != nil {
log.Fatal(err)
}
if err := os.WriteFile("openapi.json", result.JSON, 0644); err != nil {
log.Fatal(err)
}
log.Println("OpenAPI specification generated: openapi.json")
}
API with Query Parameters and Pagination
package main
import (
"context"
"log"
"rivaas.dev/openapi"
)
type ListUsersRequest struct {
Page int `query:"page" doc:"Page number" example:"1" validate:"min=1"`
PerPage int `query:"per_page" doc:"Items per page" example:"20" validate:"min=1,max=100"`
Sort string `query:"sort" doc:"Sort field" enum:"name,email,created_at"`
Order string `query:"order" doc:"Sort order" enum:"asc,desc"`
Tags []string `query:"tags" doc:"Filter by tags"`
Active *bool `query:"active" doc:"Filter by active status"`
}
type User struct {
ID int `json:"id"`
Name string `json:"name"`
Email string `json:"email"`
Active bool `json:"active"`
Tags []string `json:"tags"`
}
type PaginatedResponse struct {
Data []User `json:"data"`
Page int `json:"page"`
PerPage int `json:"per_page"`
TotalPages int `json:"total_pages"`
TotalItems int `json:"total_items"`
}
func main() {
api := openapi.MustNew(
openapi.WithTitle("Paginated API", "1.0.0"),
openapi.WithInfoDescription("API with pagination and filtering"),
)
result, err := api.Generate(context.Background(),
openapi.GET("/users",
openapi.WithSummary("List users with pagination"),
openapi.WithDescription("Retrieve paginated list of users with filtering"),
openapi.WithResponse(200, PaginatedResponse{}),
),
)
if err != nil {
log.Fatal(err)
}
// Use result...
}
Multi-Source Parameters
package main
import (
"context"
"log"
"rivaas.dev/openapi"
)
type CreateOrderRequest struct {
// Path parameter
UserID int `path:"user_id" doc:"User ID" example:"123"`
// Query parameters
Coupon string `query:"coupon" doc:"Coupon code" example:"SAVE20"`
SendEmail *bool `query:"send_email" doc:"Send confirmation email"`
// Header parameters
IdempotencyKey string `header:"Idempotency-Key" doc:"Idempotency key"`
// Request body
Items []OrderItem `json:"items" validate:"required,min=1"`
Total float64 `json:"total" validate:"required,min=0"`
Notes string `json:"notes,omitempty"`
}
type OrderItem struct {
ProductID int `json:"product_id" validate:"required"`
Quantity int `json:"quantity" validate:"required,min=1"`
Price float64 `json:"price" validate:"required,min=0"`
}
type Order struct {
ID int `json:"id"`
UserID int `json:"user_id"`
Items []OrderItem `json:"items"`
Total float64 `json:"total"`
Status string `json:"status" enum:"pending,processing,completed,cancelled"`
}
func main() {
api := openapi.MustNew(
openapi.WithTitle("E-commerce API", "1.0.0"),
)
result, err := api.Generate(context.Background(),
openapi.POST("/users/:user_id/orders",
openapi.WithSummary("Create order"),
openapi.WithDescription("Create a new order for a user"),
openapi.WithRequest(CreateOrderRequest{}),
openapi.WithResponse(201, Order{}),
),
)
if err != nil {
log.Fatal(err)
}
// Use result...
}
Composable Options Pattern
package main
import (
"context"
"log"
"rivaas.dev/openapi"
)
type User struct {
ID int `json:"id"`
Name string `json:"name"`
}
type ErrorResponse struct {
Code int `json:"code"`
Message string `json:"message"`
}
// Define reusable option sets
var (
// Common error responses
CommonErrors = openapi.WithOptions(
openapi.WithResponse(400, ErrorResponse{}),
openapi.WithResponse(401, ErrorResponse{}),
openapi.WithResponse(500, ErrorResponse{}),
)
// Authenticated user endpoints
UserEndpoint = openapi.WithOptions(
openapi.WithTags("users"),
openapi.WithSecurity("bearerAuth"),
CommonErrors,
)
// JSON content type
JSONContent = openapi.WithOptions(
openapi.WithConsumes("application/json"),
openapi.WithProduces("application/json"),
)
// Read operations
ReadOperation = openapi.WithOptions(
UserEndpoint,
JSONContent,
)
// Write operations
WriteOperation = openapi.WithOptions(
UserEndpoint,
JSONContent,
openapi.WithResponse(404, ErrorResponse{}),
)
)
func main() {
api := openapi.MustNew(
openapi.WithTitle("Composable API", "1.0.0"),
openapi.WithBearerAuth("bearerAuth", "JWT authentication"),
)
result, err := api.Generate(context.Background(),
openapi.GET("/users/:id",
ReadOperation,
openapi.WithSummary("Get user"),
openapi.WithResponse(200, User{}),
),
openapi.POST("/users",
WriteOperation,
openapi.WithSummary("Create user"),
openapi.WithRequest(User{}),
openapi.WithResponse(201, User{}),
),
openapi.PUT("/users/:id",
WriteOperation,
openapi.WithSummary("Update user"),
openapi.WithRequest(User{}),
openapi.WithResponse(200, User{}),
),
)
if err != nil {
log.Fatal(err)
}
// Use result...
}
OAuth2 with Multiple Flows
package main
import (
"context"
"log"
"rivaas.dev/openapi"
)
type User struct {
ID int `json:"id"`
Name string `json:"name"`
}
func main() {
api := openapi.MustNew(
openapi.WithTitle("OAuth2 API", "1.0.0"),
// Authorization code flow (for web apps)
openapi.WithOAuth2(
"oauth2AuthCode",
"OAuth2 authorization code flow",
openapi.OAuth2Flow{
Type: openapi.FlowAuthorizationCode,
AuthorizationURL: "https://auth.example.com/authorize",
TokenURL: "https://auth.example.com/token",
Scopes: map[string]string{
"read": "Read access",
"write": "Write access",
"admin": "Admin access",
},
},
),
// Client credentials flow (for service-to-service)
openapi.WithOAuth2(
"oauth2ClientCreds",
"OAuth2 client credentials flow",
openapi.OAuth2Flow{
Type: openapi.FlowClientCredentials,
TokenURL: "https://auth.example.com/token",
Scopes: map[string]string{
"api": "API access",
},
},
),
)
result, err := api.Generate(context.Background(),
// Public endpoint
openapi.GET("/health",
openapi.WithSummary("Health check"),
openapi.WithResponse(200, nil),
),
// User-facing endpoint (auth code flow)
openapi.GET("/users/:id",
openapi.WithSummary("Get user"),
openapi.WithSecurity("oauth2AuthCode", "read"),
openapi.WithResponse(200, User{}),
),
// Service endpoint (client credentials flow)
openapi.POST("/users/sync",
openapi.WithSummary("Sync users"),
openapi.WithSecurity("oauth2ClientCreds", "api"),
openapi.WithResponse(200, nil),
),
)
if err != nil {
log.Fatal(err)
}
// Use result...
}
Version-Aware API with Diagnostics
package main
import (
"context"
"fmt"
"log"
"rivaas.dev/openapi"
"rivaas.dev/openapi/diag"
)
type User struct {
ID int `json:"id"`
Name string `json:"name"`
}
func main() {
api := openapi.MustNew(
openapi.WithTitle("Version-Aware API", "1.0.0"),
openapi.WithVersion(openapi.V30x), // Target 3.0.x
openapi.WithInfoSummary("API with 3.1 features"), // 3.1-only feature
)
result, err := api.Generate(context.Background(),
openapi.GET("/users/:id",
openapi.WithSummary("Get user"),
openapi.WithResponse(200, User{}),
),
)
if err != nil {
log.Fatal(err)
}
// Handle warnings
if result.Warnings.Has(diag.WarnDownlevelInfoSummary) {
fmt.Println("Note: info.summary was dropped (3.1 feature with 3.0 target)")
}
// Filter by category
downlevelWarnings := result.Warnings.FilterCategory(diag.CategoryDownlevel)
if len(downlevelWarnings) > 0 {
fmt.Printf("Downlevel warnings: %d\n", len(downlevelWarnings))
for _, warn := range downlevelWarnings {
fmt.Printf(" [%s] %s\n", warn.Code(), warn.Message())
}
}
// Fail on unexpected warnings
expected := []diag.WarningCode{
diag.WarnDownlevelInfoSummary,
}
unexpected := result.Warnings.Exclude(expected...)
if len(unexpected) > 0 {
log.Fatalf("Unexpected warnings: %d", len(unexpected))
}
fmt.Println("Specification generated successfully")
}
Complete Production Example
package main
import (
"context"
"fmt"
"log"
"os"
"time"
"rivaas.dev/openapi"
"rivaas.dev/openapi/diag"
)
// Domain models
type User struct {
ID int `json:"id" doc:"User ID"`
Name string `json:"name" doc:"User's full name"`
Email string `json:"email" doc:"Email address"`
Role string `json:"role" doc:"User role" enum:"admin,user,guest"`
Active bool `json:"active" doc:"Whether user is active"`
CreatedAt time.Time `json:"created_at" doc:"Creation timestamp"`
UpdatedAt time.Time `json:"updated_at" doc:"Last update timestamp"`
}
type CreateUserRequest struct {
Name string `json:"name" validate:"required"`
Email string `json:"email" validate:"required,email"`
Role string `json:"role" validate:"required" enum:"admin,user,guest"`
}
type ErrorResponse struct {
Code int `json:"code"`
Message string `json:"message"`
Details string `json:"details,omitempty"`
Timestamp time.Time `json:"timestamp"`
}
// Reusable option sets
var (
CommonErrors = openapi.WithOptions(
openapi.WithResponse(400, ErrorResponse{}),
openapi.WithResponse(401, ErrorResponse{}),
openapi.WithResponse(500, ErrorResponse{}),
)
UserEndpoint = openapi.WithOptions(
openapi.WithTags("users"),
openapi.WithSecurity("bearerAuth"),
CommonErrors,
)
)
func main() {
api := openapi.MustNew(
// Basic info
openapi.WithTitle("User Management API", "2.1.0"),
openapi.WithInfoDescription("Production-ready API for managing users and permissions"),
openapi.WithTermsOfService("https://example.com/terms"),
// Contact
openapi.WithContact(
"API Support",
"https://example.com/support",
"api-support@example.com",
),
// License
openapi.WithLicense("Apache 2.0", "https://www.apache.org/licenses/LICENSE-2.0.html"),
// Version
openapi.WithVersion(openapi.V31x),
// Servers
openapi.WithServer("https://api.example.com/v2", "Production"),
openapi.WithServer("https://staging-api.example.com/v2", "Staging"),
openapi.WithServer("http://localhost:8080/v2", "Development"),
// Security
openapi.WithBearerAuth("bearerAuth", "JWT token authentication"),
// Tags
openapi.WithTag("users", "User management operations"),
// Extensions
openapi.WithExtension("x-api-version", "2.1"),
openapi.WithExtension("x-environment", os.Getenv("ENVIRONMENT")),
// Enable validation
openapi.WithValidation(true),
)
result, err := api.Generate(context.Background(),
// Public endpoints
openapi.GET("/health",
openapi.WithSummary("Health check"),
openapi.WithDescription("Check API health status"),
openapi.WithResponse(200, map[string]string{"status": "ok"}),
),
// User CRUD operations
openapi.GET("/users",
UserEndpoint,
openapi.WithSummary("List users"),
openapi.WithDescription("Retrieve paginated list of users"),
openapi.WithResponse(200, []User{}),
),
openapi.GET("/users/:id",
UserEndpoint,
openapi.WithSummary("Get user"),
openapi.WithDescription("Retrieve a specific user by ID"),
openapi.WithResponse(200, User{}),
openapi.WithResponse(404, ErrorResponse{}),
),
openapi.POST("/users",
UserEndpoint,
openapi.WithSummary("Create user"),
openapi.WithDescription("Create a new user"),
openapi.WithRequest(CreateUserRequest{}),
openapi.WithResponse(201, User{}),
),
openapi.PUT("/users/:id",
UserEndpoint,
openapi.WithSummary("Update user"),
openapi.WithDescription("Update an existing user"),
openapi.WithRequest(CreateUserRequest{}),
openapi.WithResponse(200, User{}),
openapi.WithResponse(404, ErrorResponse{}),
),
openapi.DELETE("/users/:id",
UserEndpoint,
openapi.WithSummary("Delete user"),
openapi.WithDescription("Delete a user"),
openapi.WithResponse(204, nil),
openapi.WithResponse(404, ErrorResponse{}),
),
)
if err != nil {
log.Fatalf("Generation failed: %v", err)
}
// Handle warnings
if len(result.Warnings) > 0 {
fmt.Printf("Generated with %d warnings:\n", len(result.Warnings))
for _, warn := range result.Warnings {
fmt.Printf(" [%s] %s at %s\n",
warn.Code(),
warn.Message(),
warn.Path(),
)
}
}
// Write specification files
if err := os.WriteFile("openapi.json", result.JSON, 0644); err != nil {
log.Fatal(err)
}
if err := os.WriteFile("openapi.yaml", result.YAML, 0644); err != nil {
log.Fatal(err)
}
fmt.Printf("✓ Generated OpenAPI %s specification\n", api.Version())
fmt.Printf("✓ JSON: openapi.json (%d bytes)\n", len(result.JSON))
fmt.Printf("✓ YAML: openapi.yaml (%d bytes)\n", len(result.YAML))
}
Next Steps