OpenAPI

Generate OpenAPI/Swagger documentation automatically with Forge's built-in support

Forge provides comprehensive OpenAPI (Swagger) support with automatic documentation generation, schema validation, and interactive API exploration. The framework generates OpenAPI specifications from your route definitions and provides built-in endpoints for API documentation.

OpenAPI Interface

Forge's OpenAPI interface provides automatic documentation generation:

type OpenAPIManager interface {
    // Schema generation
    GenerateSpec() (*openapi3.T, error)
    AddSchema(name string, schema interface{}) error
    
    // Route documentation
    DocumentRoute(route RouteInfo) error
    DocumentGroup(group GroupInfo) error
    
    // Customization
    SetInfo(info OpenAPIInfo) error
    SetServers(servers []OpenAPIServer) error
    
    // Validation
    ValidateRequest(schema interface{}, data interface{}) error
    ValidateResponse(schema interface{}, data interface{}) error
}

Automatic Documentation Generation

Basic OpenAPI Setup

func main() {
    app := forge.NewApp(forge.AppConfig{
        Name: "my-api",
        Version: "1.0.0",
    })
    
    // Enable OpenAPI documentation
    app.Router().Use(forge.OpenAPIMiddleware(forge.OpenAPIConfig{
        Title:       "My API",
        Version:     "1.0.0",
        Description: "A comprehensive API for managing resources",
        Contact: forge.OpenAPIContact{
            Name:  "API Support",
            Email: "support@example.com",
        },
        License: forge.OpenAPILicense{
            Name: "MIT",
            URL:  "https://opensource.org/licenses/MIT",
        },
    }))
    
    // Your routes will be automatically documented
    app.Router().GET("/users", getUsersHandler)
    app.Router().POST("/users", createUserHandler)
    
    app.Run()
}

Route Documentation

// Documented route with schemas
app.Router().POST("/users", createUserHandler,
    forge.WithRequestSchema(CreateUserRequest{}),
    forge.WithResponseSchema(201, "User created successfully", UserResponse{}),
    forge.WithResponseSchema(400, "Bad request", ErrorResponse{}),
    forge.WithResponseSchema(422, "Validation error", ValidationErrorResponse{}),
    forge.WithTags("users"),
    forge.WithSummary("Create a new user"),
    forge.WithDescription("Creates a new user account with the provided information"),
)

// Documented route with path parameters
app.Router().GET("/users/:id", getUserHandler,
    forge.WithResponseSchema(200, "User details", UserResponse{}),
    forge.WithResponseSchema(404, "User not found", ErrorResponse{}),
    forge.WithTags("users"),
    forge.WithSummary("Get user by ID"),
    forge.WithDescription("Retrieves user information by user ID"),
)

Schema Definitions

Request/Response Schemas

// User schemas
type User struct {
    ID        uuid.UUID `json:"id" description:"Unique user identifier" format:"uuid"`
    Email     string    `json:"email" description:"User email address" format:"email"`
    Name      string    `json:"name" description:"User full name" minLength:"2" maxLength:"100"`
    CreatedAt time.Time `json:"created_at" description:"User creation timestamp" format:"date-time"`
    UpdatedAt time.Time `json:"updated_at" description:"User last update timestamp" format:"date-time"`
}

type CreateUserRequest struct {
    Email    string `json:"email" description:"User email address" format:"email" validate:"required,email"`
    Name     string `json:"name" description:"User full name" minLength:"2" maxLength:"100" validate:"required"`
    Password string `json:"password" description:"User password" minLength:"8" validate:"required"`
}

type UpdateUserRequest struct {
    Name     string `json:"name" description:"User full name" minLength:"2" maxLength:"100"`
    Password string `json:"password" description:"User password" minLength:"8"`
}

type UserResponse struct {
    ID        uuid.UUID `json:"id" description:"Unique user identifier" format:"uuid"`
    Email     string    `json:"email" description:"User email address" format:"email"`
    Name      string    `json:"name" description:"User full name"`
    CreatedAt time.Time `json:"created_at" description:"User creation timestamp" format:"date-time"`
    UpdatedAt time.Time `json:"updated_at" description:"User last update timestamp" format:"date-time"`
}

type ErrorResponse struct {
    Error   string `json:"error" description:"Error message"`
    Code    int    `json:"code" description:"Error code"`
    Details string `json:"details,omitempty" description:"Additional error details"`
}

Complex Schemas

// Pagination schema
type PaginatedResponse struct {
    Data       []UserResponse `json:"data" description:"Array of users"`
    Pagination PaginationInfo `json:"pagination" description:"Pagination information"`
}

type PaginationInfo struct {
    Page       int  `json:"page" description:"Current page number" minimum:"1"`
    PerPage    int  `json:"per_page" description:"Items per page" minimum:"1" maximum:"100"`
    Total      int  `json:"total" description:"Total number of items"`
    TotalPages int  `json:"total_pages" description:"Total number of pages"`
    HasNext    bool `json:"has_next" description:"Whether there is a next page"`
    HasPrev    bool `json:"has_prev" description:"Whether there is a previous page"`
}

// Filter schema
type UserFilter struct {
    Name     string `json:"name,omitempty" description:"Filter by name (partial match)"`
    Email    string `json:"email,omitempty" description:"Filter by email (partial match)"`
    CreatedAfter  *time.Time `json:"created_after,omitempty" description:"Filter users created after this date" format:"date-time"`
    CreatedBefore *time.Time `json:"created_before,omitempty" description:"Filter users created before this date" format:"date-time"`
}

Advanced OpenAPI Features

Custom Schema References

// Define reusable schemas
app.Router().AddSchemaRef("User", User{})
app.Router().AddSchemaRef("Error", ErrorResponse{})
app.Router().AddSchemaRef("Pagination", PaginationInfo{})

// Use schema references in routes
app.Router().GET("/users", getUsersHandler,
    forge.WithResponseSchema(200, "List of users", forge.SchemaRef("PaginatedResponse")),
    forge.WithResponseSchema(400, "Bad request", forge.SchemaRef("Error")),
)

Request Examples

app.Router().POST("/users", createUserHandler,
    forge.WithRequestSchema(CreateUserRequest{}),
    forge.WithRequestExample("valid_user", CreateUserRequest{
        Email:    "john@example.com",
        Name:     "John Doe",
        Password: "securepassword123",
    }),
    forge.WithRequestExample("minimal_user", CreateUserRequest{
        Email:    "jane@example.com",
        Name:     "Jane",
        Password: "password123",
    }),
)

Response Examples

app.Router().GET("/users/:id", getUserHandler,
    forge.WithResponseSchema(200, "User details", UserResponse{}),
    forge.WithResponseExample(200, "successful_user", UserResponse{
        ID:        uuid.New(),
        Email:     "john@example.com",
        Name:      "John Doe",
        CreatedAt: time.Now(),
        UpdatedAt: time.Now(),
    }),
    forge.WithResponseSchema(404, "User not found", ErrorResponse{}),
    forge.WithResponseExample(404, "not_found", ErrorResponse{
        Error: "User not found",
        Code: 404,
    }),
)

Authentication Documentation

// Define security schemes
app.Router().AddSecurityScheme("BearerAuth", forge.SecurityScheme{
    Type:        "http",
    Scheme:      "bearer",
    BearerFormat: "JWT",
    Description: "JWT token authentication",
})

app.Router().AddSecurityScheme("ApiKeyAuth", forge.SecurityScheme{
    Type:        "apiKey",
    In:          "header",
    Name:        "X-API-Key",
    Description: "API key authentication",
})

// Apply security to routes
app.Router().GET("/users", getUsersHandler,
    forge.WithAuth("BearerAuth"),
    forge.WithResponseSchema(200, "List of users", []UserResponse{}),
    forge.WithResponseSchema(401, "Unauthorized", ErrorResponse{}),
)

app.Router().POST("/users", createUserHandler,
    forge.WithAuth("ApiKeyAuth"),
    forge.WithRequestSchema(CreateUserRequest{}),
    forge.WithResponseSchema(201, "User created", UserResponse{}),
)

OpenAPI Endpoints

Built-in Endpoints

Forge automatically provides OpenAPI endpoints:

# OpenAPI specification
GET /_/openapi.json

# Swagger UI
GET /_/swagger

# ReDoc documentation
GET /_/redoc

# OpenAPI YAML
GET /_/openapi.yaml

Custom Documentation Endpoints

// Custom OpenAPI endpoint
app.Router().GET("/docs/openapi", func(ctx forge.Context) error {
    spec := app.Router().GenerateOpenAPISpec()
    
    // Customize spec
    spec.Info.Title = "My Custom API"
    spec.Info.Version = "2.0.0"
    
    return ctx.JSON(200, spec)
})

// Custom Swagger UI
app.Router().GET("/docs/swagger", func(ctx forge.Context) error {
    html := generateSwaggerUI("/docs/openapi")
    return ctx.HTML(200, html)
})

Schema Validation

Request Validation

func createUserHandler(ctx forge.Context) error {
    var req CreateUserRequest
    
    // Bind and validate request
    if err := ctx.BindJSON(&req); err != nil {
        return forge.BadRequest("invalid JSON")
    }
    
    // Validate against OpenAPI schema
    if err := app.Router().ValidateRequest(CreateUserRequest{}, req); err != nil {
        return forge.BadRequest("validation failed")
    }
    
    // Process request
    user, err := userService.CreateUser(req)
    if err != nil {
        return forge.InternalError("failed to create user")
    }
    
    return ctx.JSON(201, user)
}

Response Validation

func getUserHandler(ctx forge.Context) error {
    id := ctx.Param("id")
    
    user, err := userService.GetUser(id)
    if err != nil {
        return forge.NotFound("user not found")
    }
    
    // Validate response against schema
    if err := app.Router().ValidateResponse(UserResponse{}, user); err != nil {
        app.Logger().Warn("response validation failed", forge.F("error", err))
    }
    
    return ctx.JSON(200, user)
}

OpenAPI Configuration

Basic Configuration

app := forge.NewApp(forge.AppConfig{
    OpenAPIConfig: forge.OpenAPIConfig{
        Enabled:     true,
        Title:       "My API",
        Version:     "1.0.0",
        Description: "A comprehensive API for managing resources",
        Contact: forge.OpenAPIContact{
            Name:  "API Support",
            Email: "support@example.com",
            URL:   "https://example.com/support",
        },
        License: forge.OpenAPILicense{
            Name: "MIT",
            URL:  "https://opensource.org/licenses/MIT",
        },
        Servers: []forge.OpenAPIServer{
            {
                URL:         "https://api.example.com",
                Description: "Production server",
            },
            {
                URL:         "https://staging-api.example.com",
                Description: "Staging server",
            },
        },
    },
})

Advanced Configuration

openAPIConfig := forge.OpenAPIConfig{
    // Basic info
    Enabled:     true,
    Title:       "My API",
    Version:     "1.0.0",
    Description: "A comprehensive API for managing resources",
    
    // Contact information
    Contact: forge.OpenAPIContact{
        Name:  "API Support",
        Email: "support@example.com",
        URL:   "https://example.com/support",
    },
    
    // License information
    License: forge.OpenAPILicense{
        Name: "MIT",
        URL:  "https://opensource.org/licenses/MIT",
    },
    
    // Servers
    Servers: []forge.OpenAPIServer{
        {
            URL:         "https://api.example.com",
            Description: "Production server",
        },
    },
    
    // Security schemes
    SecuritySchemes: map[string]forge.SecurityScheme{
        "BearerAuth": {
            Type:        "http",
            Scheme:      "bearer",
            BearerFormat: "JWT",
        },
        "ApiKeyAuth": {
            Type: "apiKey",
            In:   "header",
            Name: "X-API-Key",
        },
    },
    
    // Custom settings
    CustomSettings: map[string]interface{}{
        "enable_validation": true,
        "strict_mode":       false,
        "generate_examples": true,
    },
}

Testing OpenAPI

OpenAPI Specification Testing

func TestOpenAPISpec(t *testing.T) {
    app := forge.NewTestApp(forge.TestAppConfig{
        Name: "test-app",
    })
    
    // Register routes
    app.Router().GET("/users", func(ctx forge.Context) error {
        return ctx.JSON(200, []UserResponse{})
    })
    
    app.Router().POST("/users", func(ctx forge.Context) error {
        return ctx.JSON(201, UserResponse{})
    })
    
    // Generate OpenAPI spec
    spec := app.Router().GenerateOpenAPISpec()
    
    // Validate spec
    assert.NotNil(t, spec)
    assert.Equal(t, "test-app", spec.Info.Title)
    assert.Contains(t, spec.Paths, "/users")
    
    // Check paths
    usersPath := spec.Paths["/users"]
    assert.NotNil(t, usersPath.Get)
    assert.NotNil(t, usersPath.Post)
}

Documentation Endpoint Testing

func TestOpenAPIEndpoint(t *testing.T) {
    app := forge.NewTestApp(forge.TestAppConfig{
        Name: "test-app",
    })
    
    // Start application
    go func() {
        app.Run()
    }()
    defer app.Stop(context.Background())
    
    // Test OpenAPI endpoint
    resp, err := http.Get("http://localhost:8080/_/openapi.json")
    require.NoError(t, err)
    defer resp.Body.Close()
    
    assert.Equal(t, 200, resp.StatusCode)
    assert.Equal(t, "application/json", resp.Header.Get("Content-Type"))
    
    // Parse OpenAPI spec
    var spec map[string]interface{}
    err = json.NewDecoder(resp.Body).Decode(&spec)
    require.NoError(t, err)
    
    assert.Equal(t, "test-app", spec["info"].(map[string]interface{})["title"])
}

Best Practices

  1. Schema Design: Design clear, consistent schemas
  2. Documentation: Provide comprehensive descriptions
  3. Examples: Include realistic examples
  4. Validation: Use schema validation for requests/responses
  5. Security: Document authentication and authorization
  6. Versioning: Use proper API versioning
  7. Testing: Test OpenAPI generation and validation
  8. Standards: Follow OpenAPI 3.0 standards

For more information about API documentation and validation, see the Routing documentation.

How is this guide?

Last updated on