generator

package
v1.33.1 Latest Latest
Warning

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

Go to latest
Published: Nov 12, 2026 License: MIT Imports: 20 Imported by: 0

Documentation

Overview

Package generator derives an OpenAPI 3.0 document from the source of an Echo application built on compose's rest handlers. It reads the code purely syntactically (go/parser, no type checking): route files give the operations, handler bodies give the request type, the path and query parameters and the content types, and the request type's module function gives the response type. Everything application-specific comes in through Config.

Index

Constants

View Source
const DeprecatedOperationSuffix = "Deprecated"

DeprecatedOperationSuffix ends the operationId of a deprecated route aliasing a handler that a live route already names its operation after.

View Source
const RequestVariantSuffix = "Input"

RequestVariantSuffix names the request-side copy of a component that requests and responses share but that cannot require the same fields on both: a model the server always serializes in full, also accepted in a request body, is application_models_finance_IncomeToInvoice in responses and application_models_finance_IncomeToInvoiceInput in requests.

Variables

View Source
var DefaultSummaryVerbs = []string{
	"Add", "Cancel", "Check", "Compute", "Create", "Delete", "Deliver", "Get", "Issue", "List",
	"Login", "Logout", "Overview", "Register", "Remove", "Report", "Rollback", "Send", "Sync",
	"Unissue", "Update", "Upgrade", "Upload",
}

DefaultSummaryVerbs are the leading words that already make a summary read as an action.

Functions

func DefaultParamGetters

func DefaultParamGetters() map[string]ParamGetter

DefaultParamGetters returns the path parameter readers of echo and compose's BaseHandler.

func DefaultQueryGetters

func DefaultQueryGetters() map[string]QueryGetter

DefaultQueryGetters returns the query readers of echo and compose's BaseHandler.

func ExtendGenerated

func ExtendGenerated(extra map[string]any, omit ...string) map[string]any

ExtendGenerated returns a Config.TypeSchemas entry that is the schema the generator derives from the Go type, changed by extra: extra's properties are added to the type's own ones, replacing those of the same name, its required names are added to the type's required list, any other keyword of extra replaces the derived one, and the omit properties are left out.

It describes the usual custom MarshalJSON, which marshals an alias of the type inside a struct that adds or shadows a few fields:

cfg.TypeSchemas = map[string]map[string]any{
	"example.com/app/models.User": generator.ExtendGenerated(map[string]any{
		"properties": map[string]any{"role": roleObject},
		"required":   []any{"role"},
	}),
}

func Run

func Run(cfg Config) error

Run generates the document and writes it to cfg.OutDir: the public spec (Spec.Public) as openapi.json and openapi.yaml, the artifact SDKs and releases consume, and the full spec, internal operations included, as openapi.internal.json and openapi.internal.yaml.

Types

type Config

type Config struct {
	// Root is the module root, the directory holding go.mod. Defaults to the working directory.
	Root string
	// RoutesGlob matches the files declaring routes, e.g. "application/interfaces/rest/routes_*.go".
	// Route declarations must fit on one line: `x := parent.Group("/prefix", ...)` and
	// `x.GET("/path", h.Method, ...)`.
	RoutesGlob string
	// HandlersFile holds the struct whose `Name *pkg.Handler` fields are the handler namespaces,
	// routed as `h.Name.Method` (nested mode). Empty selects flat mode: one handler type routed as
	// `h.Method`, with the namespace taken from the first segment of the route path.
	HandlersFile string
	// HandlersStruct names the struct in HandlersFile. Defaults to "Handlers".
	HandlersStruct string
	// HandlerGlob matches the handler sources, e.g. "application/interfaces/rest/handler/*/*.go".
	HandlerGlob string
	// HandlerReceiver is the receiver type of handler methods. Defaults to "Handler".
	HandlerReceiver string
	// ReceiverVar is the variable route files call handlers through. Defaults to "h".
	ReceiverVar string
	// RootGroups maps the route files' root variables to their base path. Defaults to {"e": ""}.
	RootGroups map[string]string
	// OutDir is where Run writes openapi.json and openapi.yaml (the public spec) and
	// openapi.internal.json and openapi.internal.yaml (the full spec), relative to Root.
	OutDir string

	Info     Info
	Servers  []Server
	Security []SecurityScheme
	// ExternalDocs links the document to documentation kept elsewhere.
	ExternalDocs *ExternalDocs

	// RequiredPackagePrefix marks the packages whose structs are server-serialized models,
	// matched as a substring of the import path, e.g. "/application/models/".
	//
	// Every struct's non-omitempty, non-nilable fields are required where only responses reach
	// it, and nothing derived is required where a request reaches it (Go reads a missing key as
	// the zero value). A model is always treated as server-serialized: when requests and
	// responses share it, the component keeps its required list for the responses and the
	// requests reference a copy without it, named with RequestVariantSuffix
	// ("...IncomeToInvoiceInput"). A non-model type both sides share requires nothing.
	// readOnly properties stay required on both sides, and required lists a Schemas hook wrote
	// are left as they are.
	RequiredPackagePrefix string
	// TypeSchemas replaces the schema of a Go type, keyed by "importpath.TypeName", e.g.
	// {"github.com/shopspring/decimal.Decimal": {"type": "string", "format": "decimal"}}. The
	// type keeps its component, named as usual; the entry wins over the generator's mappings.
	// A struct that defines MarshalJSON (or embeds one that does) and a generic type must be
	// described here, generation fails otherwise. ExtendGenerated builds an entry from the
	// derived schema.
	TypeSchemas map[string]map[string]any
	// SchemaPrefixCollapse maps a module-relative package path prefix to the path every package
	// under it is named after, e.g. {"application/interfaces/rest/handler/":
	// "application/interfaces/rest/handler"} names all handler subpackages' types alike.
	SchemaPrefixCollapse map[string]string
	// SchemaNameAliases maps a module-relative package path to the schema name prefix its types
	// (and its subpackages' types) keep, so moving a package does not rename its schemas.
	SchemaNameAliases map[string]string
	// SchemaName names the component of a Go type, replacing the default name (the import
	// path relative to the module with SchemaNameAliases and SchemaPrefixCollapse applied, then
	// the type name, e.g. application_models_finance_Invoice). Generation fails when it gives
	// two types the same name, or a type the name of the error envelope (ErrorResponse,
	// StandardError). The generator finds its own types (pagination.CursorRequest, models,
	// resources) by the Go type a component was built from, so any naming works.
	SchemaName func(importPath string, typeName string) string
	// DocComments turns Go comments into descriptions: a type's doc comment on its component
	// schema, a struct field's doc and line comments on its property, an enum constant's line
	// comment (or a doc comment starting with its name) in x-enum-descriptions and
	// x-enumDescriptions, a handler method's doc comment (else its module function's) on its
	// operation, and a package's doc comment on its tag (see TagDescriptions). Off by default:
	// comments are often notes for maintainers rather than API documentation.
	DocComments bool
	// SchemaTags picks the operation tags a component schema is filed under, written as its
	// x-tags. derived holds the tags deriveSchemaTags found for the schema's Go package (pkgPath)
	// in sorted order; nil keeps them when there is exactly one. Return nil to write none.
	SchemaTags func(name string, pkgPath string, derived []string) []string
	// ResourceTag names the child tag of a resource: a package handlers take their request types
	// from (see tagResources). entity is the Go name of the model the resource's handlers
	// return, "" when none; name is the generator's choice ("Bank accounts"). Return "" to make
	// no child tag, leaving the package's schemas under their operation tag. Nil keeps name.
	ResourceTag func(pkgPath string, entity string, name string) string

	// PathSummaries overrides the summary of the operations on an OpenAPI path.
	PathSummaries map[string]string
	// SummaryVerbs are the leading words a summary is kept as is with; other GET and POST
	// summaries get a "Get" or "Create" prefix. Defaults to DefaultSummaryVerbs.
	SummaryVerbs []string
	// MaxPageSize, when positive, is the page size the server clamps a larger limit to,
	// documented as the maximum of every limit query parameter.
	MaxPageSize int
	// TagNames overrides the tag of a namespace, e.g. {"crm": "CRM"}.
	TagNames map[string]string
	// TagDescriptions describes tags by name, operation tags and resource child tags alike.
	// A tag without an entry is described, with DocComments, by its package's doc comment
	// ("Package finance ..."): the handler package of a namespace in nested mode, the module
	// package of a resource.
	TagDescriptions map[string]string

	// QueryGetters maps the name of a call reading a query parameter to its schema. Defaults to
	// DefaultQueryGetters; extend that table to add an application's own helpers.
	QueryGetters map[string]QueryGetter
	// ParamGetters maps the name of a call reading a path parameter to its schema. Defaults to
	// DefaultParamGetters.
	ParamGetters map[string]ParamGetter
	// ResponseCalls are the calls whose last argument is the handler's request type. Defaults to
	// JSONResponse, BindedJSONResponse, BindedXMLResponse and BlobResponse.
	ResponseCalls []string
	// BodyBinders are the calls that bind a request body, so DELETE handlers using them get one.
	// Defaults to BindedJSONResponse and BindedXMLResponse.
	BodyBinders []string
	// Negotiate names the call a handler negotiates its response content type with. Handlers
	// calling it respond with NegotiatedContentTypes.
	Negotiate              string
	NegotiatedContentTypes []string
	// DeprecatedWrapper names the function wrapping deprecated route handlers, e.g.
	// "logDeprecated". Deprecated routes are documented `deprecated: true` and
	// `x-internal: true`: in the full spec for the clients still calling them, left out of the
	// public one (see Spec.Public and DeprecatedOperationSuffix).
	DeprecatedWrapper string

	// Skip leaves an operation out of the document. path is the OpenAPI path ({id}, not :id).
	Skip func(method string, path string) bool
	// Internal marks an operation as internal: it is documented with `x-internal: true` and
	// Spec.Public leaves it out. path is the OpenAPI path.
	Internal func(method string, path string) bool
	// Public marks an operation as needing no authentication (`security: []`).
	Public func(method string, path string) bool
	// MultipartPath reports whether a path's request body is multipart/form-data with files.
	MultipartPath func(path string) bool
	// ContentTypes returns the response content types the handler's compose calls leave open:
	// a BlobResponse whose content type is not a constant, or a handler without a compose
	// response call. It cannot replace what the calls pin down (JSONResponse and
	// BindedJSONResponse serve application/json, BlobResponse with a constant serves that
	// type), only add to it. nil falls back to NegotiatedContentTypes for an open BlobResponse
	// or a negotiating handler, application/json otherwise.
	ContentTypes func(r Route) []string
	// RequestSchema replaces the request schema of a handler, keyed by Route.HandlerKey.
	RequestSchema func(handlerKey string) (map[string]any, bool)
	// Schemas runs over all component schemas once they are built, before request-side
	// requiredness is stripped and before the operations are assembled.
	Schemas func(schemas map[string]map[string]any)
	// Operation runs on every operation once it is assembled. op["parameters"] is a []any
	// holding the path and pagination parameters; parameters the hook adds win by name over the
	// ones derived from the handler body, which are appended afterwards.
	Operation func(r Route, op map[string]any)
}

Config describes where an application declares its routes and handlers and how its document is shaped. Paths are relative to Root. Every hook is optional.

type Contact

type Contact struct {
	Name  string
	URL   string
	Email string
}

Contact is the info object's contact.

type ExternalDocs

type ExternalDocs struct {
	Description string
	URL         string
}

ExternalDocs is the document's externalDocs object.

type Info

type Info struct {
	Title          string
	Version        string
	Description    string
	TermsOfService string
	Contact        *Contact
	License        *License
}

Info is the document's info object.

type License

type License struct {
	Name string
	URL  string
}

License is the info object's license.

type ParamGetter

type ParamGetter struct {
	Schema  map[string]any
	NameArg int
}

ParamGetter describes a call that reads one path parameter.

type QueryGetter

type QueryGetter struct {
	// Schema is the parameter's schema.
	Schema map[string]any
	// NameArg is the position of the parameter's string-literal name among the call arguments.
	NameArg int
	// Name is the parameter's fixed name, for helpers that read one without taking it as an
	// argument (GetListLimit reads "limit"). It wins over NameArg.
	Name string
	// EnumArg, when positive, is the position of a `pkg.<Type>FromString` parse function; the
	// parameter is then an array of the Type component and Schema is ignored.
	EnumArg int
	// DefaultArg, when positive, is the position of the default value the call falls back to;
	// an integer literal there becomes the parameter's default (GetListLimit(c, 50)).
	DefaultArg int
}

QueryGetter describes a call that reads one query parameter.

type Route

type Route struct {
	Method string
	// Path is the Echo path, e.g. "/units/:id".
	Path string
	// Namespace is the handler namespace: the Handlers struct field in nested mode, the first
	// path segment in flat mode. It names the operation's tag and prefixes its operationId.
	Namespace string
	// Handler is the handler method name.
	Handler    string
	SourceFile string
	SourceLine int
	// Deprecated routes are wrapped in Config.DeprecatedWrapper; only the full spec documents them.
	Deprecated bool
	// BindsBody reports a handler calling one of Config.BodyBinders.
	BindsBody bool
	// Negotiates reports a handler calling Config.Negotiate.
	Negotiates bool
	// contains filtered or unexported fields
}

Route is one route declaration found in the route files.

func ParseRoutes

func ParseRoutes(cfg Config) ([]Route, error)

ParseRoutes returns every route declared in the files matched by cfg.RoutesGlob, sorted by path then method.

func (Route) HandlerKey

func (r Route) HandlerKey() string

HandlerKey identifies the route's handler method: "namespace.Method" in nested mode, "Method" in flat mode.

func (Route) OpenAPIPath

func (r Route) OpenAPIPath() string

OpenAPIPath is the route path in OpenAPI form: ":id" becomes "{id}" and a trailing "/*" becomes "/{path}". Like Echo, a parameter's name runs to the next "/": ":user-id" becomes "{user-id}" and ":name.ext" becomes "{name.ext}".

type SecurityScheme

type SecurityScheme struct {
	Name string
	// Type is "apiKey" (default, header named by Header) or "http".
	Type string
	// Header names the header of an apiKey scheme.
	Header string
	// Scheme is the http scheme, "bearer". Required when Type is "http".
	Scheme string
	// BearerFormat is a hint for clients, for example "opaque".
	BearerFormat string
	// Description tells clients how to obtain and send the credential.
	Description string
}

SecurityScheme is an API key sent in a request header or an http (bearer) credential. The global requirement puts every apiKey scheme in one requirement object, so they must be sent together, and each http scheme in its own, so each is an alternative credential. Public operations opt out.

type Server

type Server struct {
	URL       string
	Variables map[string]string
}

Server is one entry of the document's servers list. Variables maps each URL variable to its default value.

type Spec

type Spec struct {
	// Document is the OpenAPI document. Maps are plain map[string]any (components.schemas is a
	// map[string]map[string]any), so encoders sort their keys and the output is deterministic.
	Document map[string]any
	// Routes are all parsed routes, skipped and deprecated ones included.
	Routes []Route
	// contains filtered or unexported fields
}

Spec is a generated OpenAPI document.

func Generate

func Generate(cfg Config) (*Spec, error)

Generate builds the OpenAPI document of the application at cfg.Root.

After every hook has run, component schemas nothing reaches are pruned. A schema is reached from an operation, or from the request or response type of a routed handler (a GET request type travels as parameters, yet SDKs still name it); drop such a schema in the Schemas hook to hide it. Generation fails when a $ref does not resolve.

The document is the full spec: Internal operations are in it, marked `x-internal: true`. Spec.Public derives the spec without them.

func (*Spec) JSON

func (s *Spec) JSON() ([]byte, error)

JSON encodes the document with two-space indentation and a trailing newline.

func (*Spec) Public

func (s *Spec) Public() (*Spec, error)

Public returns a copy of the spec without its internal operations (`x-internal: true`), deprecated routes included (see Config.DeprecatedWrapper), and without the x-source of the remaining ones. Path items left empty and tags no remaining operation uses are dropped, and component schemas are pruned with Generate's rule computed over what is left: the schemas the remaining operations reach plus the request and response types of every routed handler that is not Internal (skipped routes included). That is deliberately not strict pruning, SDKs still name those types; for strict pruning, pass nil instead of s.publicRoots to pruneSchemas below. Public fails when a $ref of the result does not resolve.

func (*Spec) YAML

func (s *Spec) YAML() ([]byte, error)

YAML encodes the document with yaml.v3's default four-space indentation.

Jump to

Keyboard shortcuts

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