sp4-test-1770498663/pkg/openapi/spec.go

230 lines
6.2 KiB
Go

// Package openapi provides an OpenAPI 3.0 specification builder and documentation endpoints.
//
// It includes:
// - OpenAPISpec: A builder for constructing OpenAPI 3.0 specifications
// - Schema helpers: Typed schema constructors (String, Int, Object, Array, etc.)
// - Parameter helpers: Path, query, header parameter builders
// - Documentation: Scalar UI and JSON spec serving via EnableDocs
//
// Example:
//
// spec := openapi.NewOpenAPISpec("My Service", "1.0.0").
// WithDescription("Service API documentation").
// WithBearerSecurity("bearer", "JWT authentication")
//
// spec.AddPath("/api/v1/items", "get", map[string]any{
// "summary": "List items",
// "tags": []string{"Items"},
// "responses": map[string]any{
// "200": openapi.OpResponse("Success", openapi.RefArray("Item")),
// },
// })
//
// application.EnableDocs(spec)
package openapi
import (
"encoding/json"
"sync"
)
// OpenAPIInfo contains metadata about the API.
type OpenAPIInfo struct {
Title string `json:"title"`
Description string `json:"description,omitempty"`
Version string `json:"version"`
}
// OpenAPIServer describes a server endpoint.
type OpenAPIServer struct {
URL string `json:"url"`
Description string `json:"description,omitempty"`
}
// OpenAPIComponents contains reusable schema definitions.
type OpenAPIComponents struct {
Schemas map[string]any `json:"schemas,omitempty"`
SecuritySchemes map[string]any `json:"securitySchemes,omitempty"`
}
// OpenAPISpec represents a minimal OpenAPI 3.0 specification.
type OpenAPISpec struct {
OpenAPI string `json:"openapi"`
Info OpenAPIInfo `json:"info"`
Servers []OpenAPIServer `json:"servers,omitempty"`
Paths map[string]map[string]any `json:"paths"`
Tags []OpenAPITag `json:"tags,omitempty"`
Components *OpenAPIComponents `json:"components,omitempty"`
Security []map[string][]string `json:"security,omitempty"`
mu sync.RWMutex
}
// OpenAPITag groups operations together.
type OpenAPITag struct {
Name string `json:"name"`
Description string `json:"description,omitempty"`
}
// NewOpenAPISpec creates a new OpenAPI specification builder.
func NewOpenAPISpec(title, version string) *OpenAPISpec {
return &OpenAPISpec{
OpenAPI: "3.0.3",
Info: OpenAPIInfo{
Title: title,
Version: version,
},
Paths: make(map[string]map[string]any),
}
}
// WithDescription sets the API description.
func (s *OpenAPISpec) WithDescription(desc string) *OpenAPISpec {
s.Info.Description = desc
return s
}
// WithServer adds a server to the spec.
func (s *OpenAPISpec) WithServer(url, description string) *OpenAPISpec {
s.Servers = append(s.Servers, OpenAPIServer{
URL: url,
Description: description,
})
return s
}
// WithTag adds a tag for grouping operations.
func (s *OpenAPISpec) WithTag(name, description string) *OpenAPISpec {
s.Tags = append(s.Tags, OpenAPITag{
Name: name,
Description: description,
})
return s
}
// AddPath adds an operation to the spec.
// method should be lowercase (get, post, put, patch, delete).
func (s *OpenAPISpec) AddPath(path, method string, operation map[string]any) *OpenAPISpec {
s.mu.Lock()
defer s.mu.Unlock()
if s.Paths[path] == nil {
s.Paths[path] = make(map[string]any)
}
s.Paths[path][method] = operation
return s
}
// ensureComponents initializes the Components field if nil.
// Must be called while holding s.mu.
func (s *OpenAPISpec) ensureComponents() {
if s.Components == nil {
s.Components = &OpenAPIComponents{
Schemas: make(map[string]any),
SecuritySchemes: make(map[string]any),
}
}
}
// WithSchema adds a reusable schema to components/schemas.
func (s *OpenAPISpec) WithSchema(name string, schema Schema) *OpenAPISpec {
s.mu.Lock()
defer s.mu.Unlock()
s.ensureComponents()
if s.Components.Schemas == nil {
s.Components.Schemas = make(map[string]any)
}
s.Components.Schemas[name] = schema
return s
}
// WithAPIKeySecurity adds API key security scheme.
func (s *OpenAPISpec) WithAPIKeySecurity(name, headerName, description string) *OpenAPISpec {
s.mu.Lock()
defer s.mu.Unlock()
s.ensureComponents()
if s.Components.SecuritySchemes == nil {
s.Components.SecuritySchemes = make(map[string]any)
}
s.Components.SecuritySchemes[name] = map[string]any{
"type": "apiKey",
"in": "header",
"name": headerName,
"description": description,
}
return s
}
// WithBearerSecurity adds Bearer token security scheme.
func (s *OpenAPISpec) WithBearerSecurity(name, description string) *OpenAPISpec {
s.mu.Lock()
defer s.mu.Unlock()
s.ensureComponents()
if s.Components.SecuritySchemes == nil {
s.Components.SecuritySchemes = make(map[string]any)
}
s.Components.SecuritySchemes[name] = map[string]any{
"type": "http",
"scheme": "bearer",
"bearerFormat": "JWT",
"description": description,
}
return s
}
// WithGlobalSecurity sets global security requirements.
func (s *OpenAPISpec) WithGlobalSecurity(schemeName string) *OpenAPISpec {
s.mu.Lock()
defer s.mu.Unlock()
s.Security = append(s.Security, map[string][]string{
schemeName: {},
})
return s
}
// JSON returns the spec as JSON bytes.
func (s *OpenAPISpec) JSON() ([]byte, error) {
s.mu.RLock()
defer s.mu.RUnlock()
return json.MarshalIndent(s, "", " ")
}
// Op creates an OpenAPI operation helper.
func Op(summary, description string, tags ...string) map[string]any {
return map[string]any{
"summary": summary,
"description": description,
"tags": tags,
"responses": map[string]any{
"200": map[string]any{"description": "Success"},
},
}
}
// OpWithBody creates an OpenAPI operation with a request body.
func OpWithBody(summary, description string, tags ...string) map[string]any {
return map[string]any{
"summary": summary,
"description": description,
"tags": tags,
"requestBody": map[string]any{
"required": true,
"content": map[string]any{
"application/json": map[string]any{
"schema": map[string]any{
"type": "object",
},
},
},
},
"responses": map[string]any{
"200": map[string]any{"description": "Success"},
"201": map[string]any{"description": "Created"},
},
}
}