openapi

package module
v0.2.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: May 5, 2026 License: MIT Imports: 19 Imported by: 0

README

Fox OpenAPI

English | 简体中文

OpenAPI 3.0.3 generator and CLI for Fox.

The recommended workflow is fox-openapi: expose a function that builds a *fox.Engine, then generate a committed openapi.yaml during development or CI. Business code does not need to mount OpenAPI handlers or import this module unless it uses optional OpenAPI metadata hooks or the library API directly.

Install

go install github.com/fox-gonic/openapi/cmd/fox-openapi@latest

For local development in this repository:

go run ./cmd/fox-openapi version

Quickstart

Expose an entry function that registers routes and returns a *fox.Engine. It should not call Run, open listeners, or start infrastructure that is not needed for route registration.

package server

import "github.com/fox-gonic/fox"

func NewEngine() *fox.Engine {
	engine := fox.New()
	engine.GET("/users/:id", getUser)
	return engine
}

Create fox-openapi.yaml in your application root:

# Auto-discover the entry function in the current module
fox-openapi init

# Or specify it explicitly
fox-openapi init --entry internal/server.NewEngine --title "Acme API"

Then generate, verify, and preview the committed spec:

fox-openapi generate                       # auto-discovers entry from ./...
fox-openapi generate ./internal/server     # narrow scope to a directory
fox-openapi check
fox-openapi serve --addr 127.0.0.1:8765

When entry is omitted from the config and --entry is not passed, the CLI scans sources (default ./...) for an exported function whose signature matches one of the supported entry shapes. If exactly one is found, it is used. If multiple are found, the CLI fails with the candidate list — pick one with --entry or annotate the canonical entry with a doc comment marker:

// NewEngine builds the production HTTP engine.
//
// fox-openapi:entry
func NewEngine() *fox.Engine { ... }

When at least one function carries the fox-openapi:entry marker, only marked candidates are considered, so adding the marker disambiguates without deleting other entry-shaped helpers.

serve exposes /openapi.yaml, /openapi.json, /docs, /scalar, and /redoc with embedded offline UI assets.

For small projects, skip the config file and pass flags:

fox-openapi generate \
  --entry github.com/acme/myapp/internal/server.NewEngine \
  --out api/openapi.yaml \
  --title "Acme API"

The CLI builds an isolated temporary driver. For basic generation, the application module does not need a tools.go file or a committed direct github.com/fox-gonic/openapi requirement; the driver build resolves that temporary dependency and restores go.mod/go.sum afterward. Add a direct requirement only when application code imports OpenAPI metadata hooks or library APIs.

Entry Functions

entry must name an exported function with one of these signatures:

func NewEngine() *fox.Engine
func NewEngine() (*fox.Engine, error)
func NewEngine(context.Context) *fox.Engine
func NewEngine(context.Context) (*fox.Engine, error)
func NewEngine(context.Context, *Config) *fox.Engine
func NewEngine(context.Context, *Config) (*fox.Engine, error)

For config-taking entries, omit entryConfig to pass nil as the config argument, or provide a loader:

entryConfig:
  loader: github.com/acme/myapp/internal/config.Load
  path: config.yaml

Passing nil lets production code share one route-registration entry with OpenAPI generation without initializing databases or external providers.

Path resolution

Paths follow standard go-tooling conventions:

  • CLI flags (--out, --config, --workdir, --entry-config-path): relative to the current working directory (where you invoked the command).
  • YAML fields (out, entryConfig.path, workdir): relative to the directory containing the config file, so fox-openapi.yaml and the artefacts it points to keep a stable layout regardless of where you run.
  • Positional path (fox-openapi generate ./internal/aone): narrows where the CLI looks for the entry function. It does not narrow source scanning — sources (default ./...) still drives comment extraction so field/handler docs in sub-packages outside the entry directory are preserved. To override scanning explicitly, set sources in YAML or pass --source.
cd ~/myapp
fox-openapi generate internal/aone --out api/openapi.yaml
# wrote ~/myapp/api/openapi.yaml   ← relative to CWD, not the scanned dir

Config

fox-openapi init writes a config like:

entry: github.com/acme/myapp/internal/server.NewEngine
out: api/openapi.yaml
sources:
  - ./...
info:
  title: Acme API
  version: 1.0.0
servers:
  - url: https://api.acme.com

Supported config keys:

  • entry: entry function. Optional — when omitted, the CLI auto-discovers an exported function with a supported signature from sources. Set explicitly to override discovery, or use // fox-openapi:entry in source.
  • out: output file, default api/openapi.yaml.
  • format: yaml or json; inferred from out when omitted.
  • sources: source directories for Go doc comments; default ./....
  • includeTestFiles: include _test.go while scanning source comments.
  • info: title, version, description.
  • servers: list of url and optional description.
  • tags: top-level OpenAPI tag registry.
  • securitySchemes: serializable HTTP, API key, OAuth2, or OpenID Connect schemes.
  • metadataHook: optional advanced Go hook.
  • entryConfig: optional loader and path for config-taking entries.

CLI flags override config values. Config values override defaults.

Commands

fox-openapi init --entry internal/server.NewEngine --title "Acme API"
fox-openapi generate --entry github.com/acme/myapp/internal/server.NewEngine --out api/openapi.yaml --title "Acme API"
fox-openapi check
fox-openapi serve --addr 127.0.0.1:8765
fox-openapi version

generate, check, and serve share the common config flags:

  • --config: config file path, default fox-openapi.yaml.
  • --entry: entry function.
  • --out: output path, default api/openapi.yaml.
  • --format: yaml or json.
  • --title and --version: OpenAPI info metadata.
  • --server: repeatable OpenAPI server URL.
  • --source: repeatable Go source path for comment extraction.
  • --include-test-files: include _test.go files while scanning comments.
  • --metadata-hook: optional func() []openapi.Option.
  • --entry-config-loader and --entry-config-path: command-line form of entryConfig.
  • --workdir: user project root.
  • --keep-driver: keep the generated temporary driver for debugging.

serve also supports --addr, repeatable --ui, --watch, and --open.

Metadata

The generator reads regular Go comments from source files to fill operation summaries, operation descriptions, and schema field descriptions. It does not require doc tags.

type CreateUserRequest struct {
	// Display name for the new user.
	Name string `json:"name" binding:"required"`
}

// Create user.
//
// Creates a user and returns the persisted representation.
func createUser(ctx *fox.Context, req CreateUserRequest) (UserResponse, error) {
	return UserResponse{}, nil
}

For metadata that needs Go values, add a small optional hook:

func ConfigureOpenAPI() []openapi.Option {
	return []openapi.Option{
		openapi.Group("/users", openapi.Tags("users")),
		openapi.Operation("GET", "/users/:id", openapi.Security("BearerAuth")),
	}
}

Then configure it:

metadataHook: github.com/acme/myapp/internal/openapimeta.ConfigureOpenAPI

Library Usage

The library mount API is useful for dev-time experiments, but the CLI is recommended for production artifacts.

package main

import (
	"github.com/fox-gonic/fox"
	"github.com/fox-gonic/openapi"
)

func main() {
	router := fox.Default()
	router.GET("/users/:id", getUser)

	spec := openapi.New(router,
		openapi.Info("My API", "1.0.0"),
		openapi.Server("https://api.example.com"),
		openapi.Source([]string{"."}),
		openapi.Operation("GET", "/users/:id", openapi.Tags("users")),
	)

	openapi.Mount(router, spec)
	router.Run(":8080")
}

By default, openapi.Mount registers /openapi.yaml and /openapi.json. You can also write artifacts directly:

if err := spec.WriteYAML(file); err != nil {
	panic(err)
}

yamlData, err := spec.YAML()
jsonData, err := spec.JSON()

Generation is best-effort. Non-fatal issues are collected as warnings:

for _, warning := range spec.Warnings() {
	log.Println(warning)
}

Generated Output

The generator covers:

  • OpenAPI version 3.0.3
  • info, servers, top-level tags, and security schemes
  • paths and methods from registered Fox routes
  • Gin-style path parameters such as /users/:id as /users/{id}
  • uri, query, header, json, and form request fields
  • operation and schema descriptions from source comments
  • explicit operation and group metadata
  • JSON, form, string, empty, and error responses
  • reusable component schemas with recursive $ref support
  • custom type schema overrides through openapi.RegisterFormatter

Supported validation tags include required, email, url, uri, uuid, uuid4, min, max, gte, lte, gt, lt, len, oneof, and alphanum.

CI

- name: Generate OpenAPI spec
  run: fox-openapi generate
- name: Verify spec is up to date
  run: git diff --exit-code api/openapi.yaml

Troubleshooting

  • entry is required: no entry provided and auto-discovery found 0 or multiple candidates. Set entry in fox-openapi.yaml, pass --entry, or add // fox-openapi:entry to the canonical function.
  • Exit code 2: generated driver failed to build. Check imports, replaces, and entry/hook signatures.
  • Exit code 3: the driver built but failed at runtime. Check entry side effects or returned errors.
  • Exit code 4: check found drift; run fox-openapi generate and commit the updated spec.

Current Limitations

The current implementation intentionally does not generate DomainEngine-specific multi-host specs, custom schema naming overrides, or operation/group tag assignment directly from YAML config. Use metadataHook for route-specific metadata.

Documentation

Overview

Package openapi generates OpenAPI specs from Fox routes and handler signatures.

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

func ApplySpecMetadata

func ApplySpecMetadata(spec *openapi3.T, metadata SpecMetadata)

ApplySpecMetadata applies top-level metadata to a generated OpenAPI model.

func HTTPBearerSecurity

func HTTPBearerSecurity(description string) *openapi3.SecurityScheme

HTTPBearerSecurity creates an HTTP bearer security scheme.

func JSONHandler

func JSONHandler(g *Generator) fox.HandlerFunc

JSONHandler returns a Fox handler that serves the generated JSON spec.

func MarshalSpecJSON

func MarshalSpecJSON(spec *openapi3.T) ([]byte, error)

MarshalSpecJSON serializes an OpenAPI model as formatted JSON.

func MarshalSpecYAML

func MarshalSpecYAML(spec *openapi3.T) ([]byte, error)

MarshalSpecYAML serializes an OpenAPI model as YAML.

func Mount

func Mount(router Router, g *Generator, opts ...MountOption)

Mount registers YAML and JSON spec endpoints on the router.

router accepts *fox.Engine or *fox.RouterGroup. Mount forces spec generation immediately so the spec endpoints themselves are not included in the generated paths. Routes registered after Mount are not picked up unless the caller invokes Generator.Regenerate().

func YAMLHandler

func YAMLHandler(g *Generator) fox.HandlerFunc

YAMLHandler returns a Fox handler that serves the generated YAML spec.

Types

type Generator

type Generator struct {
	// contains filtered or unexported fields
}

Generator builds and serializes an OpenAPI specification for a Fox engine.

Generation is lazy: routes registered after New() are still picked up on the next Spec()/JSON()/YAML() call. Schemas are cached across regenerations so repeated calls only re-walk the route table.

func New

func New(engine *fox.Engine, opts ...Option) *Generator

New creates a Generator and immediately scans the engine's current routes.

func (*Generator) JSON

func (g *Generator) JSON() ([]byte, error)

JSON serializes the generated spec as formatted JSON.

func (*Generator) Regenerate

func (g *Generator) Regenerate()

Regenerate forces a full re-scan of the engine's routes on the next access. Useful when routes are added dynamically and the caller wants the next JSON()/YAML() call to reflect them without retaining stale state.

func (*Generator) Spec

func (g *Generator) Spec() *openapi3.T

Spec returns the generated OpenAPI model. The first call walks the engine's route table; subsequent calls also re-walk so freshly registered routes are reflected.

func (*Generator) Warnings

func (g *Generator) Warnings() []string

Warnings returns non-fatal generation warnings, triggering generation if it has not yet happened.

func (*Generator) WriteYAML

func (g *Generator) WriteYAML(w io.Writer) error

WriteYAML writes the generated YAML spec to w.

func (*Generator) YAML

func (g *Generator) YAML() ([]byte, error)

YAML serializes the generated spec as YAML.

type MountOption

type MountOption func(*mountConfig)

MountOption configures OpenAPI spec endpoint mounting.

func MountJSON

func MountJSON(path string) MountOption

MountJSON sets the JSON spec endpoint path. Empty string disables.

func MountYAML

func MountYAML(path string) MountOption

MountYAML sets the YAML spec endpoint path. Empty string disables.

type OAuthFlowConfig

type OAuthFlowConfig struct {
	AuthorizationURL string
	TokenURL         string
	RefreshURL       string
	Scopes           map[string]string
}

OAuthFlowConfig describes a serializable OAuth2 flow.

type OAuthFlowsConfig

type OAuthFlowsConfig struct {
	Implicit          *OAuthFlowConfig
	Password          *OAuthFlowConfig
	ClientCredentials *OAuthFlowConfig
	AuthorizationCode *OAuthFlowConfig
}

OAuthFlowsConfig describes serializable OAuth2 flows.

type OperationOption

type OperationOption func(*operationDoc)

OperationOption configures explicit metadata for one operation.

func Deprecated

func Deprecated(value bool) OperationOption

Deprecated marks the operation as deprecated.

func Description

func Description(value string) OperationOption

Description sets the operation description.

func OperationID

func OperationID(value string) OperationOption

OperationID sets the operationId.

func Response

func Response(status int, body any, description string) OperationOption

Response adds or replaces a response for the operation.

func Security

func Security(name string, scopes ...string) OperationOption

Security adds a security requirement to the operation.

func Summary

func Summary(value string) OperationOption

Summary sets the operation summary.

func Tags

func Tags(values ...string) OperationOption

Tags sets the operation tags.

type Option

type Option func(*Generator)

Option configures a Generator.

func Group

func Group(prefix string, opts ...OperationOption) Option

Group adds metadata to operations whose Fox route path starts with prefix.

func Info

func Info(title, version string) Option

Info sets the OpenAPI info title and version.

func Operation

func Operation(method, path string, opts ...OperationOption) Option

Operation adds explicit metadata for a registered route.

func RegisterFormatter

func RegisterFormatter(typ reflect.Type, schema *openapi3.Schema) Option

RegisterFormatter overrides schema generation for a Go type.

func SecurityScheme

func SecurityScheme(name string, scheme *openapi3.SecurityScheme) Option

SecurityScheme registers an OpenAPI security scheme.

func SecuritySchemeFromConfig

func SecuritySchemeFromConfig(name string, scheme SecuritySchemeConfig) Option

SecuritySchemeFromConfig registers a serializable OpenAPI security scheme.

func Server

func Server(url string) Option

Server appends a server URL to the generated OpenAPI spec.

func SetErrorSchema

func SetErrorSchema(body any) Option

SetErrorSchema overrides the default error response schema.

func Source

func Source(paths []string, opts ...SourceOption) Option

Source enables Go comment extraction from the provided paths. A path ending in "/..." recursively walks the tree skipping vendor and dot-prefixed directories. Test files (_test.go) are skipped unless IncludeTestFiles is passed.

type Router

type Router interface {
	GET(relativePath string, handlers ...fox.HandlerFunc) gin.IRoutes
}

Router is the minimal surface Mount needs — both *fox.Engine and *fox.RouterGroup satisfy it.

type SecuritySchemeConfig

type SecuritySchemeConfig struct {
	Type             string
	Description      string
	Name             string
	In               string
	Scheme           string
	BearerFormat     string
	OpenIDConnectURL string
	Flows            *OAuthFlowsConfig
}

SecuritySchemeConfig describes a serializable OpenAPI security scheme.

type SourceOption

type SourceOption func(*sourceConfig)

SourceOption configures Source.

func IncludeTestFiles

func IncludeTestFiles() SourceOption

IncludeTestFiles makes Source also scan _test.go files. By default test files are skipped so comments authored in tests do not bleed into production specs; this option exists for the rare case where handler documentation lives in test fixtures.

type SpecExternalDocs

type SpecExternalDocs struct {
	Description string
	URL         string
}

SpecExternalDocs describes top-level tag external documentation.

type SpecMetadata

type SpecMetadata struct {
	InfoDescription    string
	ServerDescriptions []string
	Tags               []SpecTag
}

SpecMetadata contains top-level OpenAPI metadata used by generated drivers.

type SpecTag

type SpecTag struct {
	Name         string
	Description  string
	ExternalDocs *SpecExternalDocs
}

SpecTag describes a top-level OpenAPI tag.

Directories

Path Synopsis
cmd
fox-openapi command
internal
cli
collisionfixture/v1/users
Package users in v1 path collides on short name with v2/users.
Package users in v1 path collides on short name with v2/users.
collisionfixture/v2/users
Package users in v2 path collides on short name with v1/users.
Package users in v2 path collides on short name with v1/users.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL