// 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"}, }, } }