Skip to content

hydronica/go-openapi

Repository files navigation

GoOpenAPI

A Go SDK for building OpenAPI 3.0.3 specifications programmatically. Generate complete OpenAPI documentation from your Go code with type-safe schema generation and comprehensive security support.

codecov

Features

  • Complete OpenAPI 3.0.3 Support: Generate valid OpenAPI specifications
  • Type-Safe Schema Generation: Automatic schema creation from Go structs, maps, and JSON
  • Comprehensive Security: Support for API Key, Bearer, Basic, OAuth2, and OpenID Connect
  • Parameter Management: Path, query, header, and cookie parameters with examples
  • Request/Response Bodies: Multiple content types and examples
  • Route Management: Fluent API for building routes and operations
  • Schema Compilation: Automatic schema consolidation and reference generation

Installation

go get github.com/hydronica/go-openapi

Quick Start

Basic Usage

package main

import (
    "fmt"
    "github.com/hydronica/go-openapi"
)

func main() {
    // Create a new OpenAPI document
    doc := openapi.New("My API", "1.0.0", "A sample API")

    // Add a route
    route := doc.GetRoute("/users/{id}", "GET")
    route.AddResponse(openapi.Response{
        Status: 200,
        Desc:   "User retrieved successfully",
    }.WithExample(map[string]any{
        "id":   123,
        "name": "John Doe",
        "email": "[email protected]",
    }))

    // Generate JSON
    fmt.Println(doc.JSON())
}

From Existing JSON

//go:embed openapi.json
var baseSpec string

doc, err := openapi.NewFromJson(baseSpec)
if err != nil {
    log.Fatal(err)
}

Schema Generation

1. Go Structs (Recommended)

Use Go structs with json and desc tags for the cleanest schema generation:

type User struct {
    ID       int    `json:"id" desc:"Unique user identifier"`
    Name     string `json:"name" desc:"User's full name"`
    Email    string `json:"email" desc:"User's email address"`
    Active   bool   `json:"active" desc:"Whether the user is active"`
    Created  time.Time `json:"created" desc:"Account creation timestamp"`
}

route.AddRequest(openapi.RequestBody{}.WithExample(User{}))

Benefits:

  • Clear, readable schema names (User instead of hash)
  • Field descriptions from desc tags
  • Type-safe and refactor-friendly

2. JSON Strings

Convert JSON strings directly to schemas:

route.AddRequest(openapi.RequestBody{}.WithJSONString(`{
    "name": "John Doe",
    "age": 30,
    "email": "[email protected]"
}`))

Benefits:

  • Quick prototyping
  • Easy for simple schemas

Limitations:

  • Auto-generated hash names
  • No field descriptions

3. Example Maps

Use map[string]Example for detailed field descriptions:

route.AddRequest(openapi.RequestBody{}.WithExample(map[string]openapi.Example{
    "name": {Value: "John Doe", Desc: "User's full name"},
    "age":  {Value: 30, Desc: "User's age in years"},
    "email": {Value: "[email protected]", Desc: "User's email address"},
}))

Benefits:

  • Field-level descriptions
  • Flexible value types

Limitations:

  • Auto-generated hash names
  • More verbose syntax

Route Management

Creating Routes

// Get or create a route
route := doc.GetRoute("/users/{id}", "GET")

// Add tags for grouping
route.Tags("users", "management")

// Add summary
route.Summary = "Get user by ID"

Path Parameters

Path parameters are automatically detected from the path:

route := doc.GetRoute("/users/{id}/posts/{postId}", "GET")
// Automatically creates path parameters for 'id' and 'postId'

// Add examples for path parameters
route.PathParam("id", 123, "User ID")
route.PathParam("postId", "abc-123", "Post ID")

Query Parameters

// Single parameter
route.QueryParam("limit", 10, "Maximum number of results")
route.QueryParam("offset", 0, "Number of results to skip")

// Multiple parameters from struct
type QueryParams struct {
    Limit  int    `json:"limit" desc:"Maximum results"`
    Offset int    `json:"offset" desc:"Results to skip"`
    Search string `json:"search" desc:"Search term"`
}
route.QueryParams(QueryParams{Limit: 10, Offset: 0, Search: "example"})

// Multiple parameters from map
route.QueryParams(map[string]any{
    "limit":  10,
    "offset": 0,
    "search": "example",
})

Header Parameters

route.HeaderParam("X-API-Version", "v1", "API version")
route.HeaderParam("X-Request-ID", "abc-123", "Request identifier")

Cookie Parameters

route.CookieParam("session", "abc123", "Session identifier")

Multiple Parameter Examples

// Multiple examples for a single parameter
route.QueryParam("status", []string{"active", "inactive", "pending"}, "User status")

// Using Example struct for custom names
route.QueryParam("priority", []openapi.Example{
    {Summary: "low", Value: 1},
    {Summary: "medium", Value: 5},
    {Summary: "high", Value: 10},
}, "Priority level")

Request and Response Bodies

Request Bodies

// From struct
type CreateUserRequest struct {
    Name  string `json:"name" desc:"User's name"`
    Email string `json:"email" desc:"User's email"`
}

route.AddRequest(openapi.RequestBody{
    Desc: "User creation data",
    Required: true,
}.WithExample(CreateUserRequest{}))

// Multiple examples
route.AddRequest(openapi.RequestBody{}.
    WithNamedExample("admin", CreateUserRequest{Name: "Admin", Email: "[email protected]"}).
    WithNamedExample("user", CreateUserRequest{Name: "User", Email: "[email protected]"}))

Response Bodies

// Success response
route.AddResponse(openapi.Response{
    Status: 200,
    Desc:   "User created successfully",
}.WithExample(User{}))

// Error response
route.AddResponse(openapi.Response{
    Status: 400,
    Desc:   "Invalid request",
}.WithJSONString(`{"error": "validation failed", "details": "name is required"}`))

// Multiple status codes
route.AddResponse(openapi.Response{Status: 200, Desc: "Success"}.WithExample(User{}))
route.AddResponse(openapi.Response{Status: 404, Desc: "Not found"}.WithJSONString(`{"error": "user not found"}`))
route.AddResponse(openapi.Response{Status: 500, Desc: "Server error"}.WithJSONString(`{"error": "internal server error"}`))

Security

API Key Authentication

// Header-based API key
doc.AddAPIKeyAuth("YourUniqueNameApiKeyAuth", "X-API-Key", openapi.APIKeyInHeader, "API key for authentication")

// Query parameter API key
doc.AddAPIKeyAuth("ApiKeyQuery", "api_key", openapi.APIKeyInQuery, "API key as query parameter")

// Cookie-based API key
doc.AddAPIKeyAuth("ApiKeyCookie", "auth_token", openapi.APIKeyInCookie, "API key in cookie")

Bearer Token Authentication

doc.AddBearerAuth("BearerAuth", openapi.BearerFormatJWT, "Bearer token authentication")

Basic Authentication

doc.AddBasicAuth("BasicAuth", "HTTP Basic authentication")

OAuth2 Authentication

flows := &openapi.Flows{
    AuthorizationCode: &openapi.Flow{
        AuthorizationURL: "https://example.com/oauth/authorize",
        TokenURL:         "https://example.com/oauth/token",
        Scopes: map[string]string{
            "read":  "Read access",
            "write": "Write access",
            "admin": "Admin access",
        },
    },
}
doc.AddOAuth2Auth("OAuth2", flows, "OAuth2 authentication")

OpenID Connect

doc.AddOpenIDConnectAuth("OpenIDConnect", "https://example.com/.well-known/openid_configuration", "OpenID Connect authentication")

Security Requirements

Global Security Requirements

// Single security requirement for all endpoints
doc.AddSecurityRequirement("ApiKeyAuth", []string{})

// OAuth2 with scopes for all endpoints
doc.AddSecurityRequirement("OAuth2", []string{"read", "write"})

// Multiple security schemes (AND logic) for all endpoints
doc.AddMultipleSecurityRequirement(map[string][]string{
    "ApiKeyAuth": {},
    "OAuth2":     {"read"},
})

Route-Specific Security Requirements

// Add security to individual routes
route := doc.GetRoute("/admin/users", "POST")

// Single security requirement for this route only
route.AddSecurity("OAuth2Auth", "admin", "write")

// Multiple security schemes for this route (AND logic)
route.AddMultipleSecurity(map[string][]string{
    "BearerAuth": {},
    "ApiKeyAuth": {},
})

// Different routes can have different security requirements
publicRoute := doc.GetRoute("/public", "GET")
// No security requirements needed for public route

protectedRoute := doc.GetRoute("/protected", "GET")
protectedRoute.AddSecurity("BearerAuth")

adminRoute := doc.GetRoute("/admin", "DELETE")
adminRoute.AddSecurity("OAuth2Auth", "admin")

// Clear security from a route if needed
route.ClearSecurity()

Available Constants

The library provides constants for common security values:

// Security scheme types
openapi.SecurityTypeAPIKey    // "apiKey"
openapi.SecurityTypeHTTP      // "http"
openapi.SecurityTypeOAuth2    // "oauth2"
openapi.SecurityTypeOpenID    // "openIdConnect"

// HTTP authentication schemes
openapi.HTTPSchemeBearer     // "bearer"
openapi.HTTPSchemeBasic      // "basic"

// API key locations
openapi.APIKeyInQuery        // "query"
openapi.APIKeyInHeader       // "header"
openapi.APIKeyInCookie       // "cookie"

// Common bearer token formats
openapi.BearerFormatJWT // "JWT"

Advanced Features

Tags

doc.AddTags(
    openapi.Tag{
        Name: "users",
        Desc: "User management operations",
    },
    openapi.Tag{
        Name: "posts",
        Desc: "Post management operations",
    },
)

Servers

doc.Servers = []openapi.Server{
    {
        URL:  "https://api.example.com/v1",
        Desc: "Production server",
    },
    {
        URL:  "https://staging-api.example.com/v1",
        Desc: "Staging server",
    },
}

Path Conversion

Convert Go-style paths to OpenAPI format:

// Convert ":id" to "{id}"
path := openapi.CleanPath("/users/:id/posts/:postId")
// Result: "/users/{id}/posts/{postId}"

Custom Time Formatting

type Event struct {
    Name string           `json:"name"`
    Date openapi.Time     `json:"date"`
}

event := Event{
    Name: "Meeting",
    Date: openapi.Time{Time: time.Now(), Format: "2006-01-02"},
}

Schema Compilation

Compile the document to consolidate schemas and validate:

if err := doc.Compile(); err != nil {
    log.Printf("Validation errors: %v", err)
}

Custom Schema Names

// Set custom name for JSON schema
jsonData := openapi.JSONString(`{"name": "value"}`).SetName("CustomSchema")
route.AddRequest(openapi.RequestBody{}.WithExample(jsonData))

Complete Example

package main

import (
    "fmt"
    "time"
    "github.com/hydronica/go-openapi"
)

type User struct {
    ID       int       `json:"id" desc:"Unique user identifier"`
    Name     string    `json:"name" desc:"User's full name"`
    Email    string    `json:"email" desc:"User's email address"`
    Active   bool      `json:"active" desc:"Whether the user is active"`
    Created  time.Time `json:"created" desc:"Account creation timestamp"`
}

type CreateUserRequest struct {
    Name  string `json:"name" desc:"User's name"`
    Email string `json:"email" desc:"User's email"`
}

type ErrorResponse struct {
    Error   string `json:"error" desc:"Error message"`
    Details string `json:"details" desc:"Error details"`
}

func main() {
    // Create document
    doc := openapi.New("User API", "1.0.0", "A simple user management API")

    // Add security schemes
    doc.AddBearerAuth("BearerAuth", openapi.BearerFormatJWT, "Bearer token authentication")

    // Add OAuth2 for admin endpoints
    flows := &openapi.Flows{
        AuthorizationCode: &openapi.Flow{
            AuthorizationURL: "https://example.com/oauth/authorize",
            TokenURL:         "https://example.com/oauth/token",
            Scopes: map[string]string{
                "read":  "Read access to user data",
                "write": "Write access to user data",
                "admin": "Administrative access",
            },
        },
    }
    doc.AddOAuth2Auth("OAuth2Auth", flows, "OAuth2 authentication for admin operations")

    // Add tags
    doc.AddTags(openapi.Tag{
        Name: "users",
        Desc: "User management operations",
    })

    // GET /users
    listRoute := doc.GetRoute("/users", "GET")
    listRoute.Tags("users")
    listRoute.Summary = "List users"
    listRoute.QueryParam("limit", 10, "Maximum number of users to return")
    listRoute.QueryParam("offset", 0, "Number of users to skip")
    listRoute.AddResponse(openapi.Response{
        Status: 200,
        Desc:   "List of users",
    }.WithExample([]User{{ID: 1, Name: "John Doe", Email: "[email protected]", Active: true}}))

    // POST /users
    createRoute := doc.GetRoute("/users", "POST")
    createRoute.Tags("users")
    createRoute.Summary = "Create user"
    createRoute.AddRequest(openapi.RequestBody{
        Desc:     "User creation data",
        Required: true,
    }.WithExample(CreateUserRequest{Name: "Jane Doe", Email: "[email protected]"}))
    createRoute.AddResponse(openapi.Response{
        Status: 201,
        Desc:   "User created successfully",
    }.WithExample(User{ID: 2, Name: "Jane Doe", Email: "[email protected]", Active: true}))
    createRoute.AddResponse(openapi.Response{
        Status: 400,
        Desc:   "Invalid request",
    }.WithExample(ErrorResponse{Error: "validation failed", Details: "name is required"}))

    // GET /users/{id} - requires bearer auth
    getRoute := doc.GetRoute("/users/{id}", "GET")
    getRoute.Tags("users")
    getRoute.Summary = "Get user by ID"
    getRoute.PathParam("id", 123, "User ID")
    getRoute.AddSecurity("BearerAuth") // Route-specific security
    getRoute.AddResponse(openapi.Response{
        Status: 200,
        Desc:   "User details",
    }.WithExample(User{ID: 1, Name: "John Doe", Email: "[email protected]", Active: true}))
    getRoute.AddResponse(openapi.Response{
        Status: 404,
        Desc:   "User not found",
    }.WithExample(ErrorResponse{Error: "not found", Details: "user with id 123 not found"}))

    // DELETE /users/{id} - requires admin OAuth2 scope
    deleteRoute := doc.GetRoute("/users/{id}", "DELETE")
    deleteRoute.Tags("users")
    deleteRoute.Summary = "Delete user"
    deleteRoute.PathParam("id", 123, "User ID")
    deleteRoute.AddSecurity("OAuth2Auth", "admin") // Admin-only endpoint
    deleteRoute.AddResponse(openapi.Response{
        Status: 204,
        Desc:   "User deleted successfully",
    })
    deleteRoute.AddResponse(openapi.Response{
        Status: 403,
        Desc:   "Insufficient permissions",
    }.WithExample(ErrorResponse{Error: "forbidden", Details: "admin scope required"}))

    // GET /public/info - no security required
    publicRoute := doc.GetRoute("/public/info", "GET")
    publicRoute.Tags("public")
    publicRoute.Summary = "Get public information"
    publicRoute.AddResponse(openapi.Response{
        Status: 200,
        Desc:   "Public information",
    }.WithJSONString(`{"version": "1.0.0", "status": "operational"}`))
    // No security requirements for public endpoints

    // Compile and validate
    if err := doc.Compile(); err != nil {
        fmt.Printf("Validation errors: %v\n", err)
    }

    // Output JSON
    fmt.Println(doc.JSON())
}

API Reference

Core Types

  • OpenAPI: Main document structure
  • Route: Individual API endpoint
  • RequestBody: Request body definition
  • Response: Response definition
  • Param: Parameter definition
  • Schema: Data type definition
  • Example: Example value with description

Methods

Document Methods

  • New(title, version, description): Create new document
  • NewFromJson(spec): Create from existing JSON
  • GetRoute(path, method): Get or create route
  • JSON(): Generate JSON output
  • Compile(): Validate and consolidate schemas

Global Security Methods

  • AddAPIKeyAuth(name, keyName, location, description): Add API key authentication
  • AddBearerAuth(name, bearerFormat, description): Add bearer token authentication
  • AddBasicAuth(name, description): Add basic authentication
  • AddOAuth2Auth(name, flows, description): Add OAuth2 authentication
  • AddOpenIDConnectAuth(name, url, description): Add OpenID Connect authentication
  • AddSecurityRequirement(schemeName, scopes...): Add global security requirement
  • AddMultipleSecurityRequirement(schemes): Add multiple global security schemes

Route Methods

  • AddResponse(response): Add response to route
  • AddRequest(request): Add request body to route
  • AddSecurity(schemeName, scopes...): Add security requirement to specific route
  • AddMultipleSecurity(schemes): Add multiple security schemes to specific route
  • ClearSecurity(): Remove all security requirements from route
  • PathParam(name, value, desc): Add path parameter
  • QueryParam(name, value, desc): Add query parameter
  • HeaderParam(name, value, desc): Add header parameter
  • CookieParam(name, value, desc): Add cookie parameter
  • Tags(tags...): Add tags to route

Request/Response Methods

  • WithExample(value): Add example to request/response
  • WithJSONString(json): Add JSON string example
  • WithNamedExample(name, value): Add named example

Contributing

Contributions are welcome! Please feel free to submit a Pull Request.

License

This project is licensed under the MIT License - see the LICENSE file for details.

About

A Go Lang SDK to help create OpenApi 3.0.3 Spec with go-lang

Resources

License

Stars

Watchers

Forks

Packages

No packages published

Contributors 3

  •  
  •  
  •  

Languages