goai

package
v1.15.0 Latest Latest
Warning

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

Go to latest
Published: Apr 26, 2026 License: Apache-2.0 Imports: 18 Imported by: 0

README

goai — OpenAPI 3.0.3 generator for the gone framework

goai walks a gone-framework route tree and emits an OpenAPI 3.0.3 YAML document that downstream tooling (Swagger UI, Redoc, Spectral, code generators) can consume directly.

It is intentionally additive to ghttp: nothing in this package mutates existing route or handler types, so adopting goai costs nothing for code that does not register with it. Operations that do register with goai.Register get fully fleshed-out request/response schemas; the rest appear with auto-discovered paths and method shapes only.


Why goai

Existing pain goai's answer
Hand-edited OpenAPI files drift from the route tree Walk reflects the live route tree → drift is detectable on every CI run.
Code-first generators force handlers into a particular shape goai is opt-in; handlers stay vanilla ghttp.HandlerTask types.
Generators clobber hand-tuned prose, examples, and x-* tags Merge3Way keeps hand-tuned content; auto-discovered entries fill gaps only.
Per-environment specs (public vs. internal) need rebuilds Profile + Selector scope the same route tree to multiple output buckets.

OpenAPI 3.0.3 coverage

Every Object defined by OpenAPI 3.0.3 has a Go counterpart in openapi.go:

Spec object Go type Notes
OpenAPI Object Document Root, with Extensions for x-* keys.
Info Object Info Includes termsOfService, contact, license.
Contact Object Contact
License Object License
Server Object Server Supports variables.
Server Variable Object ServerVariable
Components Object Components All nine sub-maps: schemas, responses, parameters, examples, requestBodies, headers, securitySchemes, links, callbacks.
Paths Object map[string]*PathItem Map keyed by URL template.
Path Item Object PathItem Includes $ref, servers, all eight HTTP verbs.
Operation Object Operation Includes externalDocs, callbacks, servers.
External Documentation ExternalDocumentation
Parameter Object Parameter Includes deprecated, allowEmptyValue, explode, allowReserved, examples, content.
Request Body Object RequestBody
Media Type Object MediaType Includes multi-examples and encoding.
Encoding Object Encoding
Responses Object map[string]*Response
Response Object Response Includes links.
Callback Object Callback Modeled as map[string]*PathItem.
Example Object Example
Link Object Link
Header Object Header Mirrors the parameter shape (no name/in).
Tag Object Tag Includes externalDocs.
Reference Object $ref field on Schema
Schema Object Schema Full validation surface — see Schema reference.
Discriminator Object Discriminator
XML Object XML
Security Scheme Object SecurityScheme apiKey, http, oauth2, openIdConnect.
OAuth Flows Object OAuthFlows implicit, password, clientCredentials, authorizationCode.
OAuth Flow Object OAuthFlow
Security Requirement Object []map[string][]string
Specification Extensions Extensions map[string]any (inline yaml) Available on every object that allows x-* per the spec.

If a downstream validator (Spectral, Redocly Lint) flags any spec field goai cannot emit, that is a bug — please open an issue.


Installation

goai lives inside the gone module. Use it from any project that already imports gone:

import "github.com/yetiz-org/gone/goai"

There is no separate Go module. A CLI binary goai (in cmd/goai) provides emit, merge, lint, and version subcommands. Route walking still requires a project-side binary that imports the project's handler package at compile time (typically cmd/goaispec); goai emit is a convenience wrapper that locates and runs it. See Quick start below.


Quick start

1. Build a route tree
route := ghttp.NewSimpleRoute()
route.SetEndpoint("/hello", &HelloHandler{})
route.SetEndpoint("/api/v1/me", &MeHandler{})
2. Generate the spec from a CLI

Add a cmd/goaispec/main.go to your project:

package main

import (
    "github.com/yetiz-org/gone/ghttp"
    "github.com/yetiz-org/gone/goai"
    "myproject/handlers"
)

func main() {
    goai.RunCLI(
        func() ghttp.RouteEntriesProvider { return handlers.NewAppRoute() },
        goai.RunOptions{
            Title:         "My API",
            Version:       "1.0.0",
            DefaultOutput: "docs/openapi/openapi.generated.yaml",
            Servers: []goai.Server{
                {URL: "https://api.example.com"},
            },
        },
    )
}

Run it:

go run ./cmd/goaispec               # writes to DefaultOutput
go run ./cmd/goaispec -o /tmp/x.yaml
go run ./cmd/goaispec -o -          # writes to stdout
3. Serve the spec at runtime
route.SetEndpoint("/openapi.yaml", goai.Handler(route,
    goai.WithRuntimeBuildOptions(goai.BuildOptions{
        Title:   "My API",
        Version: "1.0.0",
    }),
    goai.WithRuntimeCacheTTL(30*time.Second),
))

Now GET /openapi.yaml returns the live spec.

See the examples/ directory for runnable code. Each sub-directory is a self-contained main.go:

Path What it shows
examples/quickstart/ Minimum viable pipeline: route → Walk → Build → EmitYAML.
examples/customschema/ goai.Register + struct tags (goai:"...") for typed schemas.
examples/full/ Every RunOptions field, including License, ServerVariables.
examples/runtime/ Mounting the runtime handler at /openapi.yaml.
examples/merge/ Three-way merge with a hand-tuned baseline.

Run any of them:

go run ./goai/examples/quickstart
go run ./goai/examples/full -o -
go run ./goai/examples/merge

Core concepts

The pipeline
route (RouteEntriesProvider)
        │
        ▼
   goai.Walk         → []OperationCandidate     (one per discovered HTTP op)
        │
        ▼
   goai.Build        → *Document                (filtered by optional Profile)
        │
        ▼
   goai.EmitYAML     → []byte                   (deterministic, 2-space indent)
        │
   (optional) goai.Merge3Way
        │
        ▼
        file / stdout / runtime response

RunCLI is a thin wrapper around the pipeline that adds flag parsing, output handling, and three-way merge. Use it from project-side main.go binaries; call the lower-level functions directly for non-CLI use cases (tests, code generation, runtime serving).

Walker rules

goai.Walk reflects on each handler to decide which operations to emit:

  • A handler-method counts as implemented when its source location is not <autogenerated>. This catches both direct definitions on the leaf type and hand-written overrides of an embedded default — without forcing you to register every method explicitly.
  • The walker emits one operation per (path, HTTP method).
  • Index and Get distinguish collection vs. item endpoints: a handler that defines BOTH Index and at least one of Get/Patch/Put/ Delete is "collection-style", and the item-level methods get an appended /{id} path. Handlers with only item-level methods (e.g. a /me resource that only defines Get) emit on the bare path.
Acceptance-driven security and parameters

Acceptances on a route node can implement these optional interfaces:

  • goai.SecurityProvider — declare an (scheme, scopes) requirement. The walker collects every provider on the route's acceptance chain and attaches them to each operation.
  • goai.PathParamInjector — declare path parameters this acceptance injects into the request (e.g. an OrgScope acceptance that prepends /{organization_id}).
  • goai.ProfileScope — declare which output profiles the operation belongs to (e.g. mgmt-only).

Use these when documenting cross-cutting middleware behavior. For per-handler overrides, prefer goai.Register or SpecProvider.

Per-handler customization

Two equivalent paths:

goai.Register (recommended for most cases):

goai.Register(handler.HandlerAlbums, "Post",
    (*CreateAlbumRequest)(nil),
    (*CreateAlbumResponse)(nil),
    goai.WithSummary("Create a new album"),
    goai.WithTag("Album"),
    goai.WithExample("application/json", exampleBody),
    goai.WithSecurity("OAuth2", "albums:write"),
)

SpecProvider interface (when registration is awkward, e.g. inside a codegen-managed file):

// Catch-all: applies to every HTTP method on this handler.
func (h *MeHandler) GOAISpec() goai.Spec {
    return goai.NewSpec(
        goai.WithTag("User"),
        goai.WithSecurity("OAuth2", "openid"),
    )
}

// Per-method override: only the Get() method gets this Spec, overriding
// whatever GOAISpec returned. Method names mirror the handler struct's
// HTTP method functions (Get → GOAIGetSpec, Post → GOAIPostSpec, ...).
func (h *MeHandler) GOAIGetSpec() goai.Spec {
    return goai.NewSpec(
        goai.WithSummary("Get the current user"),
        goai.WithTag("User"),
        goai.WithSecurity("OAuth2", "openid"),
    )
}

Available per-method providers: IndexSpecProvider, GetSpecProvider, CreateSpecProvider, PostSpecProvider, PatchSpecProvider, PutSpecProvider, DeleteSpecProvider, OptionsSpecProvider, TraceSpecProvider. Implement only the ones whose Go method the handler exposes.

The walker prefers Register. The SpecProvider family runs only when no registry entry exists for that handler+method. Per-method providers take precedence over the catch-all SpecProvider when both are implemented.

Profile filtering

Profile selects a subset of operations for a given output bucket. The canonical use case is splitting one OpenAPI document into several audience-specific outputs (e.g. public, internal, admin).

Operations are assigned to a profile by either:

  1. Implementing ProfileScope.GOAIProfileScope() on the handler or an acceptance to declare profile names directly.
  2. Configuring Profile.Include / Profile.Exclude Selectors that match path globs, package prefixes, or OpenAPI tags.

goai ships no built-in path → profile rules; project-specific path conventions are configured by the caller.

Pass the *Profile to Build to filter the output.

Three-way merge

goai.Merge3Way(generated, existing, overrides) overlays the generator output onto a hand-tuned baseline:

Section Merge policy
info, servers, security, externalDocs Existing wins verbatim.
tags Union — existing order preserved, generated tags missing from existing appended.
paths Union by path key — existing path entries kept verbatim; new paths appended.
components.* (every sub-map) Union by name — existing entries kept; generated entries fill gaps only.

The overrides parameter is reserved for a future explicit-override layer and is currently ignored.

This is the canonical pattern when the project keeps a curated docs/openapi/openapi.yaml as the source of truth (rich descriptions, examples, x-* extensions) and wants the generator to augment without overwriting hand-tuned prose. See examples/merge.


API reference

Top-level types
Type Purpose
RouteFactory func() ghttp.RouteEntriesProvider. Used by RunCLI.
RunOptions Project-side configuration for RunCLI.
BuildOptions Lower-level configuration for Build. Used by RunCLI and Handler.
Spec Per-operation metadata produced by Options. Immutable.
Option func(*Spec). Mutates a Spec during NewSpec / Register.
Profile Named output bucket with Include/Exclude selectors.
Selector Path / package / tag rule set.
Classifier Path → tag and acceptance-typename → security mapping.
Functional Options
Option Effect
WithSummary(s) Sets summary.
WithDescription(s) Sets description.
WithOperationID(s) Sets operationId (defaults to package + handler + method).
WithTag(tags...) Appends to tags.
WithExample(mediaType, value) Single example for a media-type.
WithExampleObject(mediaType, name, *Example) Named Example object — supports summary/externalValue.
WithRequestMediaType(mt) Override the default application/json for the request body.
WithDeprecated() Marks the operation deprecated.
WithSecurity(scheme, scopes...) Appends a security requirement.
WithHeader(HeaderDef) Documents a request or response header.
WithParam(PathParam) Documents an extra path/query/header parameter.
WithExternalDocs(url, description) Operation-level externalDocs.
WithCallback(name, Callback) Operation-level callback under callbacks.<name>.
WithOperationServer(Server) Adds an operation-level server override.
Schema reference

Validation constraints can be declared via the goai struct tag. Tokens are separated by ;, key/value pairs by =. Unknown keys are silently ignored.

type CreateAlbumRequest struct {
    Title       string   `json:"title" goai:"description=The album title;example=Greatest Hits;minLength=1;maxLength=200"`
    ReleaseYear int      `json:"release_year" goai:"description=Year of release;minimum=1900;maximum=2100"`
    Genres      []string `json:"genres,omitempty" goai:"minItems=1;uniqueItems=true"`
}
Tag key Schema field Type Notes
title title string
description,desc description string
example example string
default default string
format format string date-time, uuid, email, ...
enum enum comma-separated enum=red,green,blue.
pattern pattern regex string
minLength,maxLength string bounds unsigned int
minimum,maximum numeric bounds float
exclusiveMinimum,exclusiveMaximum bool flags =true / bare key
multipleOf numeric multiple float
minItems,maxItems array bounds unsigned int
uniqueItems array uniqueness bool
minProperties,maxProperties object bounds unsigned int
nullable OAS-3.0 nullable bool Pointer types are auto-nullable; tag overrides.
deprecated property deprecation bool
readOnly,writeOnly request/response visibility bool

The reflection layer also recognises:

  • *Tnullable: true.
  • time.Timestring/date-time.
  • []bytestring/byte (base64-encoded).
  • Embedded structs are flattened.
  • json:"-" is honored (field skipped).
  • json:"name,omitempty" controls the property name and required flag.
CLI flags (RunCLI)
Flag Default Effect
-o RunOptions.DefaultOutput Output path. - writes to stdout.
-title RunOptions.Title Override info.title.
-version RunOptions.Version Override info.version.

Tests can drive RunCLI without spawning a subprocess by supplying Args, Stdout, Stderr, and Exit on RunOptions.

Runtime handler

goai.Handler(route, opts...) returns a ghttp.HandlerTask that responds to GET with the generated YAML.

Option Effect
WithRuntimeProfile(name) Pick which profile to emit. Default "all".
WithRuntimeBuildOptions(opts) Override BuildOptions (Title, Servers, Tags, ...).
WithRuntimeCacheTTL(d) Cache the bytes for d between rebuilds. 0 disables cache.

Mount it like any other ghttp endpoint:

route.SetEndpoint("/openapi.yaml", goai.Handler(route))

Recipes

Add per-environment specs
profile := goai.Profile{
    Name:    "internal",
    Include: goai.Selector{Paths: []string{"/api/v1/internal/**", "/api/v1/webhooks/**"}},
}

candidates := goai.Walk(route)
doc := goai.Build(candidates, &profile, goai.BuildOptions{
    Title:   "Internal API",
    Version: "1.0.0",
})
Mark an endpoint deprecated
goai.Register(legacyHandler, "Get",
    nil, (*LegacyResponse)(nil),
    goai.WithDeprecated(),
    goai.WithDescription("Deprecated. Use /v2/foo instead."),
)
Document a callback
goai.Register(orderHandler, "Post",
    (*PlaceOrderRequest)(nil), (*PlaceOrderResponse)(nil),
    goai.WithCallback("orderShipped", goai.Callback{
        "{$request.body#/callbackUrl}": &goai.PathItem{
            Post: &goai.Operation{
                Summary:  "Server-to-callback URL when the order ships.",
                Responses: map[string]*goai.Response{
                    "200": {Description: "Acknowledged"},
                },
            },
        },
    }),
)
Layer hand-tuned content

Keep the curated docs/openapi/openapi.yaml checked in. Have the generator merge onto it instead of overwriting:

goai.RunCLI(
    factory,
    goai.RunOptions{
        DefaultOutput: "docs/openapi/openapi.generated.yaml",
        BaseSpecPath:  "docs/openapi/openapi.yaml",   // ← merge target
        // ... other options
    },
)

Logging

goai uses the project standard kklogger for any runtime warnings (e.g. Register called with a nil handler). Logger names follow the pattern goai:Struct.Method#section!action.


Limitations

  • goai gen (codegen of zz_goai_init.go files) is unimplemented; use manual goai.Register calls in a project-side cmd/goaispec binary invoked through goai emit (or directly via goai.RunCLI).
  • Merge3Way's overrides parameter is accepted but ignored.
  • The schema builder reads only the goai-specific goai:"..." tag; it does not consult validate:"..." tags from go-playground/validator.

License

Same license as the parent gone module. See the repository root.

Documentation

Overview

Package goai generates OpenAPI 3.0.3 documents from a gone-framework route tree without forcing handlers into a special shape.

goai is intentionally additive to ghttp: nothing in this package mutates existing route or handler types. Operations registered via goai.Register receive fully fleshed-out request/response schemas; the rest appear with auto-discovered paths and method shapes only.

Optional handler/acceptance interfaces

Implement these to refine the generated spec without touching goai's API:

  • SpecProvider — supplies one Spec applied to every HTTP method on the handler. Use for the common case where all methods share metadata.
  • <Method>SpecProvider — per-Go-method Spec providers (IndexSpecProvider, GetSpecProvider, CreateSpecProvider, PostSpecProvider, PatchSpecProvider, PutSpecProvider, DeleteSpecProvider, OptionsSpecProvider, TraceSpecProvider). Each method name mirrors the handler's Go method on the struct (Get → GOAIGetSpec, Post → GOAIPostSpec, ...). Per-method providers override SpecProvider when both are implemented.
  • SecurityProvider — declares required security scheme + scopes.
  • PathParamInjector — declares which path parameters this acceptance injects into the request (e.g. {organization_id}).
  • ProfileScope — declares which output profiles this node belongs to (e.g. mgmt-only).

Quick start

From a project's bootstrap:

goai.Register(myhandler.HandlerMe, "Get",
    nil, (*myhandler.MeGetResponse)(nil),
    goai.WithTag("User"),
    goai.WithDescription("Returns the current authenticated user."))

To serve the spec at runtime:

route.SetEndpoint("/openapi.yaml", goai.Handler(&route))

To generate a spec file from a CLI binary:

goai.RunCLI(factory, goai.RunOptions{
    Title:         "My API",
    Version:       "1.0.0",
    DefaultOutput: "docs/openapi.yaml",
})

OpenAPI 3.0.3 coverage

All Object types defined by the OpenAPI 3.0.3 specification are modeled in openapi.go — including License, ServerVariable, ExternalDocumentation, Encoding, Example, Link, Callback, Discriminator, and XML. Specification Extensions ("x-*" fields) are supported on every applicable object via the inline `Extensions map[string]any` field.

Examples

Each subdirectory under `examples/` is a runnable program demonstrating one feature: quickstart, customschema, full, runtime, merge.

See README.md for the full reference.

Index

Constants

View Source
const Version = "0.2.0"

Version reports the goai package version. It is bumped manually on every release of github.com/yetiz-org/gone that contains visible goai changes.

Variables

This section is empty.

Functions

func BuiltinClassify

func BuiltinClassify(c OperationCandidate) []string

BuiltinClassify is the no-op fallback classifier: every candidate lands in the "all" profile. Projects that need profile splits (public / internal / admin / etc.) supply their own classifier through BuildOptions, since the path conventions for those splits are project-specific.

func CountOperations

func CountOperations(doc *Document) int

CountOperations returns the total number of HTTP operations declared in the document across all paths and methods. Useful for CLI diagnostics and snapshot tests.

func EmitYAML

func EmitYAML(doc *Document) ([]byte, error)

EmitYAML serialises a Document into deterministic YAML bytes with two space indentation. Stable output is mandatory because both file diff tools and Merge3Way depend on byte-identical re-emission for unchanged inputs.

func Handler

Handler returns a ghttp.HandlerTask that responds to GET with an OpenAPI YAML document derived from the supplied route tree. POST/PATCH/ other verbs return NotImplemented.

The handler caches the emitted bytes when WithRuntimeCacheTTL is > 0. Caching is invalidated lazily on TTL expiry.

func LookupAll

func LookupAll(handler ghttp.HandlerTask) map[string]Entry

LookupAll returns every Entry registered for handler, indexed by method. The returned map is a copy and safe to iterate.

func Merge3Way

func Merge3Way(generated, existing, overrides []byte) ([]byte, error)

Merge3Way overlays an auto-generated OpenAPI document onto a hand-tuned existing document, preserving the hand-tuned content as the source of truth.

Merge policy:

  • Top-level scalars/maps (openapi, info, servers, security) are taken verbatim from `existing` when present. The hand-tuned document is the canonical author of prose and metadata.
  • `tags`: union — every tag in `existing` is kept in its original order, then any tag in `generated` whose name is missing from `existing` is appended.
  • `paths`: per-path union — every path in `existing` is kept verbatim (including hand-curated descriptions, parameters, examples, $refs). Any path present in `generated` but missing from `existing` is appended at the end of the paths map, so routes discovered in source code surface in the spec automatically without disturbing the hand-tuned prose for existing routes.
  • `components.*`: per-name union for every sub-map defined by OpenAPI 3.0.3 — schemas, responses, parameters, examples, requestBodies, headers, securitySchemes, links, callbacks. Existing entries always win; generated entries fill gaps only. Hand-tuned schema $refs always remain wired correctly.

`overrides` is reserved for a future explicit-override layer (e.g. an org-wide tag glossary YAML) and is currently ignored.

When `existing` is empty, the generated bytes are returned unchanged so the caller does not have to special-case "no baseline yet".

func Register

func Register(handler ghttp.HandlerTask, method string, req any, resp any, opts ...Option)

Register associates a Spec, request type and response type with a (handler, method) pair. method is normalised to upper-case. req and resp may be nil; if non-nil, they should be a typed-nil pointer (e.g. (*MyResp)(nil)) so that reflect can extract the element type regardless of whether the value is the zero value of a struct.

Calling Register more than once for the same handler+method overwrites the previous entry; this lets generated init() files coexist with hand-written project-level overrides.

func Reset

func Reset()

Reset clears the registry. Intended for tests.

func RunCLI

func RunCLI(factory RouteFactory, opts RunOptions)

RunCLI is the canonical entry point for project-side `cmd/goaispec` (or equivalent) binaries. It parses CLI flags, walks the supplied route tree, builds an OpenAPI 3.0.3 document, and writes the yaml to the resolved output destination.

Typical usage:

func main() {
	os.Setenv("APP_DEBUG", "true")
	goai.RunCLI(
		func() ghttp.RouteEntriesProvider { return handlers.NewAppRoute() },
		goai.RunOptions{
			Title:         "My API",
			Version:       "1.0.0",
			Servers:       []goai.Server{{URL: "https://api.example.com"}},
			DefaultOutput: "docs/openapi.yaml",
			SecuritySchemes: map[string]*goai.SecurityScheme{
				"OAuth2": {Type: "oauth2", Flows: ...},
			},
		},
	)
}

RunCLI does NOT panic on failure; it writes a diagnostic to Stderr and invokes Exit(1). Callers that want non-fatal behaviour should call Walk, Build and EmitYAML directly.

func RunCLIFromConfig

func RunCLIFromConfig(configPath string, factory RouteFactory, opts ...RunFromConfigOption)

RunCLIFromConfig is the multi-output entry point. It loads goai.yaml from configPath, walks the route tree once, and emits one yaml per declared profile in the config (or a single yaml when no profiles are declared). The hand-tuned base spec, when configured, is merged into every emitted yaml.

configPath may point at either the goai.yaml file directly or the directory containing it.

Types

type AcceptanceSecurityRule

type AcceptanceSecurityRule struct {
	// TypeName is matched against the unqualified Go type name of each
	// acceptance in the chain. Match is case-sensitive substring; the first
	// acceptance whose type name contains TypeName wins.
	TypeName string
	Scheme   string
	Scopes   []string
}

AcceptanceSecurityRule maps an acceptance type-name match to a security scheme reference.

type BuildOptions

type BuildOptions struct {
	Title          string
	Description    string
	Version        string
	TermsOfService string
	Contact        *Contact
	License        *License
	Servers        []Server
	Tags           []Tag
	GlobalSecurity []map[string][]string
	// ExternalDocs populates the top-level Document.ExternalDocs.
	ExternalDocs *ExternalDocumentation
	// Classifier returns the set of profile names a candidate belongs to
	// when the candidate did not declare any via ProfileScope. nil means
	// "use BuiltinClassify".
	Classifier func(c OperationCandidate) []string
	// TagSecurityClassifier provides default tags + security refs when a
	// candidate has no explicit Spec entry. nil means "use DefaultClassifier".
	TagSecurityClassifier *Classifier
	// SuppressEmptySchemas, when true, omits the application/json content
	// block on responses where the registered response type is nil. This
	// produces a tighter yaml when most operations are not yet registered.
	SuppressEmptySchemas bool
}

BuildOptions tunes Build behaviour.

type Callback

type Callback map[string]*PathItem

Callback is a map of expressions (e.g. "{$request.body#/callbackUrl}") to PathItem values. Per the OpenAPI 3.0.3 spec, the keys are "Runtime Expression" strings; x-* extension keys may appear at the same level alongside expression keys. yaml.v3 cannot carry two inline maps on one struct, so Callback is modeled as a plain map[string]*PathItem and any extension keys live alongside the expressions in this same map.

type Classifier

type Classifier struct {
	// PathTagRules maps a path-prefix glob to a tag name. The first matching
	// rule wins; "**" matches anything.
	PathTagRules []PathTagRule
	// AcceptanceSecurityRules maps an acceptance type-name suffix to a
	// (scheme, scopes) pair. The first matching rule per acceptance wins.
	// Type-name suffix is matched case-sensitively against the unqualified
	// Go type name of the acceptance value.
	AcceptanceSecurityRules []AcceptanceSecurityRule
}

Classifier turns a route path and its acceptance chain into reasonable default tag + security values. Builder consults it ONLY when a candidate has no explicit Spec entry from SpecProvider / goai.Register / SecurityProvider.

Classifier is the zero-config fallback: it lets a project bootstrap a spec before any handler implements SpecProvider. Once handlers self-declare metadata, project-level Classifier rules become noise — prefer the per-handler interfaces and let the rule list decay to whatever truly belongs at the framework level (static assets, well-known routes, etc.).

func DefaultClassifier

func DefaultClassifier() *Classifier

DefaultClassifier returns an empty classifier. Projects supply their own PathTagRules and AcceptanceSecurityRules through BuildOptions; goai itself has no opinion on what tags or security schemes a route should map to.

func (*Classifier) ClassifySecurity

func (c *Classifier) ClassifySecurity(acceptances []ghttp.Acceptance) []SecurityRef

ClassifySecurity returns the security refs derived from the acceptance chain. An acceptance that already implements SecurityProvider is honoured as-is; for all others, the type-name rules are consulted.

func (*Classifier) ClassifyTag

func (c *Classifier) ClassifyTag(path string) string

ClassifyTag returns the first matching tag for path, or "" if none match.

type Components

type Components struct {
	Schemas         map[string]*Schema         `yaml:"schemas,omitempty"`
	Responses       map[string]*Response       `yaml:"responses,omitempty"`
	Parameters      map[string]*Parameter      `yaml:"parameters,omitempty"`
	Examples        map[string]*Example        `yaml:"examples,omitempty"`
	RequestBodies   map[string]*RequestBody    `yaml:"requestBodies,omitempty"`
	Headers         map[string]*Header         `yaml:"headers,omitempty"`
	SecuritySchemes map[string]*SecurityScheme `yaml:"securitySchemes,omitempty"`
	Links           map[string]*Link           `yaml:"links,omitempty"`
	Callbacks       map[string]Callback        `yaml:"callbacks,omitempty"`
	Extensions      map[string]any             `yaml:",inline,omitempty"`
}

Components is the reusable definitions block.

func NewComponents

func NewComponents() *Components

NewComponents returns a Components value with all submaps preallocated.

type Config

type Config struct {
	Title           string                     `yaml:"title,omitempty"`
	Description     string                     `yaml:"description,omitempty"`
	Version         string                     `yaml:"version,omitempty"`
	TermsOfService  string                     `yaml:"termsOfService,omitempty"`
	Contact         *Contact                   `yaml:"contact,omitempty"`
	License         *License                   `yaml:"license,omitempty"`
	ExternalDocs    *ExternalDocumentation     `yaml:"externalDocs,omitempty"`
	Servers         []Server                   `yaml:"servers,omitempty"`
	Tags            []Tag                      `yaml:"tags,omitempty"`
	GlobalSecurity  []map[string][]string      `yaml:"security,omitempty"`
	SecuritySchemes map[string]*SecurityScheme `yaml:"securitySchemes,omitempty"`

	// DefaultOutput is the single-output target used when neither Profiles
	// nor Output declare anything. Empty falls back to "openapi.generated.yaml".
	DefaultOutput string `yaml:"defaultOutput,omitempty"`

	// BaseSpecPath, when set, enables three-way merge with the hand-tuned
	// base yaml.
	BaseSpecPath string `yaml:"baseSpecPath,omitempty"`

	// RestrictToBaseSpecPaths constrains the generator to paths already
	// declared in the base spec.
	RestrictToBaseSpecPaths bool `yaml:"restrictToBaseSpecPaths,omitempty"`

	// ExcludePaths is the framework-level path blocklist (static assets,
	// well-known framework routes, …). Per-handler exclusion belongs on the
	// handler itself via the Hidden interface.
	ExcludePaths []string `yaml:"excludePaths,omitempty"`

	// Profiles is keyed by profile name and contains include/exclude rules.
	Profiles map[string]ConfigProfile `yaml:"profiles,omitempty"`
	// Output describes where to write each profile's yaml. Keyed by profile
	// name; value is a relative path from the config file's directory.
	Output map[string]string `yaml:"output,omitempty"`
}

Config is the project-level goai.yaml schema. Every field has a sensible default so projects can opt in incrementally.

func DefaultConfig

func DefaultConfig() *Config

DefaultConfig returns a Config populated with profile buckets keyed off ProfileScope declarations. Selector.Profiles matches the names produced by handler / acceptance GOAIProfileScope so a handler that declares `[]string{"public"}` lands in the "public" output bucket.

func LoadConfig

func LoadConfig(dir string) (*Config, error)

LoadConfig reads goai.yaml from dir and returns a parsed Config. When the file is absent, a Config populated with builtin defaults is returned and err is nil — projects can run goai before authoring a config.

func (*Config) BuildProfile

func (c *Config) BuildProfile(name string) *Profile

BuildProfile materialises a Profile struct from one entry in c.Profiles.

func (*Config) ProfileNames

func (c *Config) ProfileNames() []string

ProfileNames returns the configured profile names in deterministic order.

func (*Config) ToBuildOptions

func (c *Config) ToBuildOptions() BuildOptions

ToBuildOptions projects the Config onto a BuildOptions, used by the multi-output driver where one Walk feeds many Build calls.

func (*Config) ToRunOptions

func (c *Config) ToRunOptions() RunOptions

ToRunOptions projects the Config onto a RunOptions for single-output generation. The Profiles / Output maps are NOT projected — multi-output drivers (RunCLIFromConfig) consult those directly.

type ConfigProfile

type ConfigProfile struct {
	Include Selector `yaml:"include,omitempty"`
	Exclude Selector `yaml:"exclude,omitempty"`
}

ConfigProfile is the on-disk shape of one profile's selector pair.

type Contact

type Contact struct {
	Name       string         `yaml:"name,omitempty"`
	URL        string         `yaml:"url,omitempty"`
	Email      string         `yaml:"email,omitempty"`
	Extensions map[string]any `yaml:",inline,omitempty"`
}

Contact is the optional API contact block.

type CreateSpecProvider

type CreateSpecProvider interface {
	GOAICreateSpec() Spec
}

CreateSpecProvider supplies the Spec for the handler's Create() method (HTTP POST that creates a collection member).

type DeleteSpecProvider

type DeleteSpecProvider interface {
	GOAIDeleteSpec() Spec
}

DeleteSpecProvider supplies the Spec for the handler's Delete() method (HTTP DELETE).

type Discriminator

type Discriminator struct {
	PropertyName string            `yaml:"propertyName"`
	Mapping      map[string]string `yaml:"mapping,omitempty"`
}

Discriminator supports oneOf/anyOf polymorphism by naming the property whose value selects the concrete sub-schema.

type Document

type Document struct {
	OpenAPI      string                 `yaml:"openapi"`
	Info         Info                   `yaml:"info"`
	Servers      []Server               `yaml:"servers,omitempty"`
	Tags         []Tag                  `yaml:"tags,omitempty"`
	Paths        map[string]*PathItem   `yaml:"paths"`
	Components   *Components            `yaml:"components,omitempty"`
	Security     []map[string][]string  `yaml:"security,omitempty"`
	ExternalDocs *ExternalDocumentation `yaml:"externalDocs,omitempty"`
	Extensions   map[string]any         `yaml:",inline,omitempty"`
}

Document is the root of an OpenAPI 3.0.3 specification.

func Build

func Build(candidates []OperationCandidate, profile *Profile, opts BuildOptions) *Document

Build assembles a single OpenAPI Document from candidates that match the supplied profile. profile may be nil, in which case all candidates pass.

type EmissionAwareParamInjector

type EmissionAwareParamInjector interface {
	InjectedParamsForEmission(routePath string, ctx EmissionContext) []PathParam
}

EmissionAwareParamInjector lets an injector tailor PathParam output per (route, emission) pair. This is required for routes whose base spec uses different placeholder shapes for collection-level Index vs item-level Get / Patch / Delete emissions — e.g. base spec entries of /<action> for Index alongside /{id}/<action> for item ops, where a single PathParam slice cannot satisfy both shapes.

When an acceptance implements EmissionAwareParamInjector, the walker calls InjectedParamsForEmission once per emission and ignores the other path-injection interfaces. Implementors typically delegate to PathAwareParamInjector or PathParamInjector when emission context is not needed.

type EmissionContext

type EmissionContext struct {
	HTTPMethod string
	GoMethod   string
	ItemLevel  bool
}

EmissionContext carries the per-emission information passed to EmissionAwareParamInjector. ItemLevel mirrors the walker's classification: false for Index / Create-style collection emissions, true for Get / Patch / Put / Delete-style item emissions where the walker normally appends a trailing /{id} placeholder.

type Encoding

type Encoding struct {
	ContentType   string             `yaml:"contentType,omitempty"`
	Headers       map[string]*Header `yaml:"headers,omitempty"`
	Style         string             `yaml:"style,omitempty"`
	Explode       *bool              `yaml:"explode,omitempty"`
	AllowReserved bool               `yaml:"allowReserved,omitempty"`
	Extensions    map[string]any     `yaml:",inline,omitempty"`
}

Encoding describes a single multipart/form-encoded property.

type Entry

type Entry struct {
	Handler  ghttp.HandlerTask
	Method   string // upper-case HTTP method
	ReqType  reflect.Type
	RespType reflect.Type
	Spec     Spec
}

Entry captures a single registration: which handler+method, and which Go types describe its request/response bodies. req or resp may be nil when the operation takes no body or returns no body.

func All

func All() []Entry

All returns every Entry currently registered, sorted by handler-key for deterministic iteration.

func Lookup

func Lookup(handler ghttp.HandlerTask, method string) (Entry, bool)

Lookup returns the Entry registered for (handler, method), if any.

type Example

type Example struct {
	Summary       string         `yaml:"summary,omitempty"`
	Description   string         `yaml:"description,omitempty"`
	Value         any            `yaml:"value,omitempty"`
	ExternalValue string         `yaml:"externalValue,omitempty"`
	Extensions    map[string]any `yaml:",inline,omitempty"`
}

Example is the OpenAPI Example Object. Use Value for inline JSON-y examples, or ExternalValue when the example lives in a separate file.

type ExternalDocumentation

type ExternalDocumentation struct {
	Description string         `yaml:"description,omitempty"`
	URL         string         `yaml:"url"`
	Extensions  map[string]any `yaml:",inline,omitempty"`
}

ExternalDocumentation references additional external documentation.

type GetSpecProvider

type GetSpecProvider interface {
	GOAIGetSpec() Spec
}

GetSpecProvider supplies the Spec for the handler's Get() method (HTTP GET — either an item read on a collection-style handler or a singleton fetch).

type Header struct {
	Description     string                `yaml:"description,omitempty"`
	Required        bool                  `yaml:"required,omitempty"`
	Deprecated      bool                  `yaml:"deprecated,omitempty"`
	AllowEmptyValue bool                  `yaml:"allowEmptyValue,omitempty"`
	Style           string                `yaml:"style,omitempty"`
	Explode         *bool                 `yaml:"explode,omitempty"`
	AllowReserved   bool                  `yaml:"allowReserved,omitempty"`
	Schema          *Schema               `yaml:"schema,omitempty"`
	Example         any                   `yaml:"example,omitempty"`
	Examples        map[string]*Example   `yaml:"examples,omitempty"`
	Content         map[string]*MediaType `yaml:"content,omitempty"`
	Extensions      map[string]any        `yaml:",inline,omitempty"`
}

Header is the response header object. Per OpenAPI 3.0.3 it is a Parameter without `name` and `in`.

type HeaderDef

type HeaderDef struct {
	Name        string
	In          string // "header" | "response"
	Description string
	Required    bool
	Example     string
}

HeaderDef declares an extra response or request header that goai should document on the operation, on top of whatever the request body or query params already produce.

type Hidden

type Hidden interface {
	Hidden() bool
}

Hidden lets a handler declare it should not appear in any generated spec. Returning true drops every method on the handler from Walker output before Build sees them. Use for framework-internal handlers (static asset servers, health probes that should not be public-documented, etc.).

type IndexSpecProvider

type IndexSpecProvider interface {
	GOAIIndexSpec() Spec
}

IndexSpecProvider supplies the Spec for the handler's Index() method (a collection-style HTTP GET that returns a list).

type Info

type Info struct {
	Title          string         `yaml:"title"`
	Description    string         `yaml:"description,omitempty"`
	TermsOfService string         `yaml:"termsOfService,omitempty"`
	Contact        *Contact       `yaml:"contact,omitempty"`
	License        *License       `yaml:"license,omitempty"`
	Version        string         `yaml:"version"`
	Extensions     map[string]any `yaml:",inline,omitempty"`
}

Info carries metadata about the API.

type ItemIDProvider

type ItemIDProvider interface {
	GOAIItemIDName() string
}

ItemIDProvider lets a handler override the path-parameter name the walker uses when auto-appending an item-level segment to a collection-style handler. The default name is `<last route segment>_id` (e.g. /albums → /albums/{albums_id}); implement this interface to choose a different name. Returning an empty string falls back to the default.

type License

type License struct {
	Name       string         `yaml:"name"`
	URL        string         `yaml:"url,omitempty"`
	Extensions map[string]any `yaml:",inline,omitempty"`
}

License is the optional API license block. Required field: Name.

type Link struct {
	OperationRef string         `yaml:"operationRef,omitempty"`
	OperationID  string         `yaml:"operationId,omitempty"`
	Parameters   map[string]any `yaml:"parameters,omitempty"`
	RequestBody  any            `yaml:"requestBody,omitempty"`
	Description  string         `yaml:"description,omitempty"`
	Server       *Server        `yaml:"server,omitempty"`
	Extensions   map[string]any `yaml:",inline,omitempty"`
}

Link Object — describes a possible design-time link from a response to another operation. OperationRef and OperationID are mutually exclusive.

type LintReport

type LintReport struct {
	Errors     []string
	Paths      int
	Operations int
}

LintReport is the structured outcome of a LintYAML pass. Errors carries human-readable messages; Paths and Operations are headline counts that callers (CI badges, status lines) can surface verbatim.

func LintYAML

func LintYAML(body []byte) (*LintReport, error)

LintYAML performs a lightweight structural check on an OpenAPI 3.0.x yaml document. The check is intentionally tighter than "did it parse" but looser than a full Spectral / Redocly validation:

  • The document MUST parse as yaml.
  • The root MUST be a mapping.
  • openapi MUST be present and start with "3.".
  • info MUST be present, be a mapping, and carry a non-empty title.
  • paths MUST be present and contain at least one entry.
  • Every entry under paths MUST be a mapping.

The intent is to catch the common "shipped a broken yaml" failure modes in CI without standing up a full validator pipeline. Returns a non-nil LintReport even when errors exist; the caller decides whether to fail.

type MediaType

type MediaType struct {
	Schema     *Schema              `yaml:"schema,omitempty"`
	Example    any                  `yaml:"example,omitempty"`
	Examples   map[string]*Example  `yaml:"examples,omitempty"`
	Encoding   map[string]*Encoding `yaml:"encoding,omitempty"`
	Extensions map[string]any       `yaml:",inline,omitempty"`
}

MediaType describes one entry under content (per media-type key).

type MethodAwareSecurityProvider

type MethodAwareSecurityProvider interface {
	SecurityRequirementFor(method string) (scheme string, scopes []string)
}

MethodAwareSecurityProvider is the per-HTTP-method variant of SecurityProvider. Returning ("", nil) for a method means "this acceptance does not contribute a security requirement on that method" — useful when the acceptance bypasses authentication for one method (e.g. RFC 7662 token introspection at POST /tokeninfo with bearer-less form auth).

method is the upper-case HTTP method ("GET", "POST", ...). Implementing MethodAwareSecurityProvider does NOT also require implementing SecurityProvider; the walker calls the method-aware variant first and only falls back to the static provider when the acceptance does not implement it.

type OAuthFlow

type OAuthFlow struct {
	AuthorizationURL string            `yaml:"authorizationUrl,omitempty"`
	TokenURL         string            `yaml:"tokenUrl,omitempty"`
	RefreshURL       string            `yaml:"refreshUrl,omitempty"`
	Scopes           map[string]string `yaml:"scopes,omitempty"`
	Extensions       map[string]any    `yaml:",inline,omitempty"`
}

OAuthFlow is one specific OAuth2 flow.

type OAuthFlows

type OAuthFlows struct {
	Implicit          *OAuthFlow     `yaml:"implicit,omitempty"`
	Password          *OAuthFlow     `yaml:"password,omitempty"`
	ClientCredentials *OAuthFlow     `yaml:"clientCredentials,omitempty"`
	AuthorizationCode *OAuthFlow     `yaml:"authorizationCode,omitempty"`
	Extensions        map[string]any `yaml:",inline,omitempty"`
}

OAuthFlows aggregates all flow types under a SecurityScheme.

type Operation

type Operation struct {
	Tags         []string               `yaml:"tags,omitempty"`
	Summary      string                 `yaml:"summary,omitempty"`
	Description  string                 `yaml:"description,omitempty"`
	ExternalDocs *ExternalDocumentation `yaml:"externalDocs,omitempty"`
	OperationID  string                 `yaml:"operationId,omitempty"`
	Parameters   []*Parameter           `yaml:"parameters,omitempty"`
	RequestBody  *RequestBody           `yaml:"requestBody,omitempty"`
	Responses    map[string]*Response   `yaml:"responses,omitempty"`
	Callbacks    map[string]Callback    `yaml:"callbacks,omitempty"`
	Deprecated   bool                   `yaml:"deprecated,omitempty"`
	// Security is a pointer so the encoder can distinguish three states:
	//   nil          → inherit document-level Security (default).
	//   &[]          → explicit "no security required"; overrides document
	//                  default with an empty array per OpenAPI 3.0.3.
	//   &[{...}, ..] → operation-level requirement set; overrides document
	//                  default with the supplied alternatives.
	Security   *[]map[string][]string `yaml:"security,omitempty"`
	Servers    []Server               `yaml:"servers,omitempty"`
	Extensions map[string]any         `yaml:",inline,omitempty"`
}

Operation describes a single HTTP operation.

type OperationCandidate

type OperationCandidate struct {
	Path        string
	Method      string
	Handler     ghttp.HandlerTask
	Acceptances []ghttp.Acceptance
	// HandlerMethod is the Go method name (Index/Get/Post/Patch/Put/Delete/
	// Options/Create) that produced this candidate. Builder uses it together
	// with Method to choose registry lookup keys and operationId components.
	HandlerMethod string
	// PathParams collected from upstream PathParamInjectors plus any leaf id
	// the walker auto-appends (item-level operations get an "id" path param).
	PathParams []PathParam
	// SecurityRefs collected from upstream SecurityProviders.
	SecurityRefs []SecurityRef
	// SecurityExplicitlyEmpty is true when at least one acceptance
	// implements MethodAwareSecurityProvider AND its generic
	// SecurityProvider would have advertised a scheme, but its method-aware
	// variant returned ("", nil) for this candidate's HTTP method. The
	// builder uses it to emit `security: []` so that the operation
	// overrides any document-level GlobalSecurity inheritance.
	SecurityExplicitlyEmpty bool
	// Profiles aggregated from ProfileScope-implementing nodes; empty means
	// "let the classifier decide".
	Profiles []string
}

OperationCandidate is one candidate operation extracted from the route tree. Builder later filters by profile and combines with registry entries to produce the final OpenAPI operation.

func Walk

Walk traverses the route tree from the supplied RouteEntriesProvider and returns one OperationCandidate per (path, supported HTTP method).

Detection is done by reflecting on the handler value: a handler-method is considered "implemented" when its function pointer differs from the corresponding default on ghttp.DefaultHTTPHandlerTask.

gone routes follow a REST-style convention where one handler can serve both a collection and an item endpoint. The walker mirrors that convention by emitting two distinct paths when both styles are present:

  • Index → bare path (collection list)
  • Create → bare path (collection create)
  • Post → bare path (collection / leaf action)
  • Get → bare path + "/{id}" when the handler is collection-style; otherwise the bare path (singleton-style)
  • Patch / Put / Delete → bare path + "/{id}" when collection-style; otherwise the bare path
  • Options → bare path

"Collection-style" means the handler overrides Index AND at least one of Get / Patch / Put / Delete. Handlers that only override item-level methods (a singleton resource exposing Get only, for example) are treated as singleton-style and emitted on the bare path.

type Option

type Option func(*Spec)

Option mutates a Spec during NewSpec or Register.

func WithCallback

func WithCallback(name string, callback Callback) Option

WithCallback registers a callback under the operation's callbacks map. `name` is the local identifier; `callback` is a map of runtime expressions (e.g. "{$request.body#/callbackUrl}") to PathItems.

func WithDeprecated

func WithDeprecated() Option

WithDeprecated marks the operation as deprecated.

func WithDescription

func WithDescription(description string) Option

WithDescription sets the long-form operation description.

func WithExample

func WithExample(mediaType string, example any) Option

WithExample attaches an example value for a given media type (e.g. "application/json").

func WithExampleObject

func WithExampleObject(mediaType, name string, ex *Example) Option

WithExampleObject attaches a named Example Object under a media type. Use this when you need richer metadata (summary, description, externalValue) than WithExample provides.

func WithExternalDocs

func WithExternalDocs(url, description string) Option

WithExternalDocs attaches an operation-level externalDocs entry.

func WithHeader

func WithHeader(h HeaderDef) Option

WithHeader documents an extra header. Use multiple times for multiple headers.

func WithOperationID

func WithOperationID(id string) Option

WithOperationID sets the operationId field. When omitted, builder synthesises it from package + handler + method.

func WithOperationServer

func WithOperationServer(server Server) Option

WithOperationServer adds a server entry that overrides the document / path-level servers for this single operation.

func WithParam

func WithParam(p PathParam) Option

WithParam documents an extra parameter (path/query/header) that goai cannot detect from the route tree alone.

func WithRequestMediaType

func WithRequestMediaType(mediaType string) Option

WithRequestMediaType overrides the default "application/json" media type used when goai auto-builds the request body schema from a registered request type. Use for operations that consume e.g. multipart/form-data or application/x-www-form-urlencoded.

func WithSecurity

func WithSecurity(scheme string, scopes ...string) Option

WithSecurity appends a SecurityRef. Multiple calls combine as logical OR at the operation level (per OpenAPI semantics).

func WithSummary

func WithSummary(summary string) Option

WithSummary sets the operation summary (one-line title shown by Swagger UI).

func WithTag

func WithTag(tags ...string) Option

WithTag appends one or more tags. Tags are used to group operations in the rendered spec and may also drive profile selectors.

type OptionsSpecProvider

type OptionsSpecProvider interface {
	GOAIOptionsSpec() Spec
}

OptionsSpecProvider supplies the Spec for the handler's Options() method (HTTP OPTIONS).

type Parameter

type Parameter struct {
	Name            string                `yaml:"name"`
	In              string                `yaml:"in"`
	Description     string                `yaml:"description,omitempty"`
	Required        bool                  `yaml:"required,omitempty"`
	Deprecated      bool                  `yaml:"deprecated,omitempty"`
	AllowEmptyValue bool                  `yaml:"allowEmptyValue,omitempty"`
	Style           string                `yaml:"style,omitempty"`
	Explode         *bool                 `yaml:"explode,omitempty"`
	AllowReserved   bool                  `yaml:"allowReserved,omitempty"`
	Schema          *Schema               `yaml:"schema,omitempty"`
	Example         any                   `yaml:"example,omitempty"`
	Examples        map[string]*Example   `yaml:"examples,omitempty"`
	Content         map[string]*MediaType `yaml:"content,omitempty"`
	Extensions      map[string]any        `yaml:",inline,omitempty"`
}

Parameter is a path/query/header/cookie parameter, or a top-level entry inside Components.Parameters. Either Schema or Content (but not both) describes the value's shape; for simple parameters use Schema, for complex media-typed ones use Content.

type PatchSpecProvider

type PatchSpecProvider interface {
	GOAIPatchSpec() Spec
}

PatchSpecProvider supplies the Spec for the handler's Patch() method (HTTP PATCH).

type PathAwareParamInjector

type PathAwareParamInjector interface {
	InjectedParamsFor(routePath string) []PathParam
}

PathAwareParamInjector is a path-aware specialization of PathParamInjector. When an injector is reused under multiple route subtrees that follow different path-parameter conventions (e.g. /api/v1/teams/{teams_id}/... vs /admin/v1/teams/{id}/...), the bare InjectedParams contract is too coarse — it cannot distinguish action endpoints under the same anchor segment from sub-resource endpoints. The walker calls InjectedParamsFor with the literal route path so the injector can return a tailored slice (or nil, to suppress injection entirely for that route).

When an acceptance implements both PathParamInjector and PathAwareParamInjector, the walker calls InjectedParamsFor and ignores InjectedParams. Implementors typically delegate to the bare method when no path-specific tailoring is needed.

type PathItem

type PathItem struct {
	Ref         string         `yaml:"$ref,omitempty"`
	Summary     string         `yaml:"summary,omitempty"`
	Description string         `yaml:"description,omitempty"`
	Get         *Operation     `yaml:"get,omitempty"`
	Put         *Operation     `yaml:"put,omitempty"`
	Post        *Operation     `yaml:"post,omitempty"`
	Delete      *Operation     `yaml:"delete,omitempty"`
	Options     *Operation     `yaml:"options,omitempty"`
	Head        *Operation     `yaml:"head,omitempty"`
	Patch       *Operation     `yaml:"patch,omitempty"`
	Trace       *Operation     `yaml:"trace,omitempty"`
	Servers     []Server       `yaml:"servers,omitempty"`
	Parameters  []*Parameter   `yaml:"parameters,omitempty"`
	Extensions  map[string]any `yaml:",inline,omitempty"`
}

PathItem holds the operations defined for a single path.

func (*PathItem) SetOperation

func (p *PathItem) SetOperation(method string, op *Operation)

SetOperation assigns op under the slot for the given upper-case method. Unknown methods are silently ignored.

type PathParam

type PathParam struct {
	Name             string
	In               string // typically "path"; "query" / "header" allowed
	Style            string // OpenAPI parameter style; default "simple"
	Description      string
	AnchorAfter      string
	Example          string
	Required         bool
	IsItemIdentifier bool
}

PathParam describes a path-level parameter injected by an upstream acceptance/minortask. AnchorAfter, when non-empty, indicates the path segment after which this parameter should be inserted; an empty AnchorAfter means "append to the end of the inherited path".

IsItemIdentifier signals that this placeholder is the item-level entity identifier for the operation. The walker's item-level emission auto-appends /{<lastSegment>_id} to a path that does not already end in a placeholder, on the assumption that the last segment is a collection that needs an item suffix. Routes whose last segment is a verb / action (e.g. /admin/v1/teams/{teams_id}/activate) opt out of that auto-suffix because the item identifier is the segment BEFORE the verb, not after it. Setting IsItemIdentifier=true on the {teams_id} param tells the walker the item id is already declared.

type PathParamInjector

type PathParamInjector interface {
	InjectedParams() []PathParam
}

PathParamInjector lets an acceptance / minortask declare which path parameters it injects into params. Builder uses this to expand the route node's static path with {param} placeholders.

type PathTagRule

type PathTagRule struct {
	Pattern string
	Tag     string
}

PathTagRule maps one path glob to a tag.

Glob syntax:

  • "*" matches a single path segment.
  • "**" matches any number of segments (including zero).
  • Literal segments match exactly.

type PostSpecProvider

type PostSpecProvider interface {
	GOAIPostSpec() Spec
}

PostSpecProvider supplies the Spec for the handler's Post() method (HTTP POST — either a bare-path action or an item-level action depending on whether the handler also defines Create()).

type Profile

type Profile struct {
	Name    string
	Include Selector
	Exclude Selector
}

Profile is one named output bucket. Operations whose path/package/tag matches Include and does not match Exclude are emitted under this profile's output file.

func (*Profile) Matches

func (p *Profile) Matches(c OperationCandidate, declaredProfiles, operationTags []string) bool

Matches returns true when the candidate should appear in this profile's output. declaredProfiles supplies the values produced by ProfileScope or the BuiltinClassify result, so explicit profile declarations are respected. operationTags supplies the OpenAPI tags attached to this operation, so tag-based selectors work as the README documents.

type ProfileScope

type ProfileScope interface {
	GOAIProfileScope() []string
}

ProfileScope lets a handler or acceptance declare which output profiles the operation belongs to. Returning an empty slice means "no preference" and lets the builder's classifier decide. The method name mirrors the interface name so its purpose is obvious at the call site.

type PutSpecProvider

type PutSpecProvider interface {
	GOAIPutSpec() Spec
}

PutSpecProvider supplies the Spec for the handler's Put() method (HTTP PUT).

type RequestBody

type RequestBody struct {
	Description string                `yaml:"description,omitempty"`
	Content     map[string]*MediaType `yaml:"content,omitempty"`
	Required    bool                  `yaml:"required,omitempty"`
	Extensions  map[string]any        `yaml:",inline,omitempty"`
}

RequestBody describes an operation request body.

type Response

type Response struct {
	Description string                `yaml:"description"`
	Headers     map[string]*Header    `yaml:"headers,omitempty"`
	Content     map[string]*MediaType `yaml:"content,omitempty"`
	Links       map[string]*Link      `yaml:"links,omitempty"`
	Extensions  map[string]any        `yaml:",inline,omitempty"`
}

Response describes a single HTTP status code response.

type RouteFactory

type RouteFactory func() ghttp.RouteEntriesProvider

RouteFactory builds the project's route tree on demand. RunCLI calls it after ghttp.SetSkipHandlerRegister(true) so route construction must not depend on real DB/cache/queue connectivity.

type RunFromConfigOption

type RunFromConfigOption func(*runFromConfigOpts)

RunFromConfigOption tunes RunCLIFromConfig.

func WithClassifier

func WithClassifier(c *Classifier) RunFromConfigOption

WithClassifier injects a fallback Classifier used when handlers have not yet implemented SpecProvider / SecurityProvider. Once every handler in the project self-declares its tag and security, drop this option.

type RunOptions

type RunOptions struct {
	// Title is the OpenAPI info.title default. CLI flag -title overrides.
	Title string

	// Description is the OpenAPI info.description.
	Description string

	// Version is the OpenAPI info.version default. CLI flag -version overrides.
	Version string

	// TermsOfService is the OpenAPI info.termsOfService URL.
	TermsOfService string

	// Contact populates Document.Info.Contact.
	Contact *Contact

	// License populates Document.Info.License (e.g. {Name: "MIT"}).
	License *License

	// ExternalDocs populates the top-level Document.ExternalDocs.
	ExternalDocs *ExternalDocumentation

	// Servers populates Document.Servers verbatim.
	Servers []Server

	// Tags populates Document.Tags verbatim. Hand-listing them controls the
	// final tag ordering in the emitted yaml.
	Tags []Tag

	// GlobalSecurity is propagated as Document.Security (the top-level
	// requirement applied to every operation that does not declare its own).
	GlobalSecurity []map[string][]string

	// TagSecurityClassifier provides per-path tag and per-acceptance security
	// fallbacks. nil → goai.DefaultClassifier() inside Build.
	TagSecurityClassifier *Classifier

	// SecuritySchemes are merged into Document.Components.SecuritySchemes
	// after Build. Wins over any classifier-injected scheme reference that
	// happens to share a name.
	SecuritySchemes map[string]*SecurityScheme

	// DefaultOutput is the -o flag default. Must be a writable file path or
	// "-" for stdout. Empty string falls back to "openapi.generated.yaml".
	DefaultOutput string

	// BaseSpec, when non-nil, is the hand-tuned OpenAPI yaml that the
	// auto-generated structure is merged onto. The hand-tuned content wins
	// for everything that is already present (info, tags, paths,
	// components.schemas, components.securitySchemes); auto-discovered
	// entries fill gaps. Use this when the project keeps a curated
	// `docs/openapi/openapi.yaml` as the canonical source of truth and
	// wants the generator to surface routes not yet documented in it.
	//
	// Mutually exclusive with BaseSpecPath; BaseSpec wins when both are set.
	BaseSpec []byte

	// BaseSpecPath, when non-empty, is the filesystem path to the
	// hand-tuned OpenAPI yaml. RunCLI loads it on startup and feeds the
	// bytes through the same merge path as BaseSpec. Empty path means "no
	// merge, emit pure auto-generated yaml".
	BaseSpecPath string

	// RestrictToBaseSpecPaths, when true and a base spec is supplied,
	// constrains the generator to ONLY emit operations whose path also
	// appears in the base spec. Auto-discovered routes that have not yet
	// been documented in the hand-tuned yaml are dropped from the output.
	//
	// Use this when the curated `docs/openapi/openapi.yaml` is treated as
	// the canonical endpoint registry — i.e. a route does not "exist" for
	// API consumers until it has been documented. This is the strict
	// inverse of the default behavior, which surfaces every walked route
	// regardless of base-spec coverage.
	RestrictToBaseSpecPaths bool

	// ExcludePaths is an explicit blocklist of path globs. Paths matching
	// any of these patterns are dropped from the generated document
	// before merge, regardless of whether RestrictToBaseSpecPaths is set.
	//
	// Glob syntax: "*" matches one segment, "**" matches any number of
	// segments. Examples:
	//
	//	{"/static/**", "/favicon.ico", "/robots.txt"} // drop static asset routes
	//	{"/debug/**"}                                  // drop debug-only routes
	ExcludePaths []string

	// Args is the argv slice the CLI parses. nil → os.Args[1:].
	Args []string

	// Stdout is where yaml goes when DefaultOutput resolves to "-". nil →
	// os.Stdout. Tests inject a buffer here.
	Stdout io.Writer

	// Stderr is where progress and error messages go. nil → os.Stderr.
	Stderr io.Writer

	// Exit is invoked on terminal failure with a non-zero code. nil →
	// os.Exit. Tests inject a recorder.
	Exit func(int)
}

RunOptions holds the project-specific spec configuration. Only Servers, Tags, GlobalSecurity and SecuritySchemes are typically project-specific — the rest have sensible defaults.

type RuntimeOption

type RuntimeOption func(*runtimeConfig)

RuntimeOption configures the http handler returned by Handler.

func WithRuntimeBuildOptions

func WithRuntimeBuildOptions(opts BuildOptions) RuntimeOption

WithRuntimeBuildOptions overrides the BuildOptions used at runtime (Title, Servers, Tags, etc.).

func WithRuntimeCacheTTL

func WithRuntimeCacheTTL(d time.Duration) RuntimeOption

WithRuntimeCacheTTL sets how long the emitted yaml is cached before re-walking the route tree. The default is 0, which disables caching (every request rebuilds — fine for local dev, tune for production).

func WithRuntimeProfile

func WithRuntimeProfile(name string) RuntimeOption

WithRuntimeProfile names the profile to emit. Defaults to "all".

type Schema

type Schema struct {
	Ref string `yaml:"$ref,omitempty"`

	Title       string `yaml:"title,omitempty"`
	Type        string `yaml:"type,omitempty"`
	Format      string `yaml:"format,omitempty"`
	Description string `yaml:"description,omitempty"`

	// Numeric validation
	MultipleOf       *float64 `yaml:"multipleOf,omitempty"`
	Maximum          *float64 `yaml:"maximum,omitempty"`
	ExclusiveMaximum bool     `yaml:"exclusiveMaximum,omitempty"`
	Minimum          *float64 `yaml:"minimum,omitempty"`
	ExclusiveMinimum bool     `yaml:"exclusiveMinimum,omitempty"`

	// String validation
	MaxLength *uint64 `yaml:"maxLength,omitempty"`
	MinLength *uint64 `yaml:"minLength,omitempty"`
	Pattern   string  `yaml:"pattern,omitempty"`

	// Array validation
	MaxItems    *uint64 `yaml:"maxItems,omitempty"`
	MinItems    *uint64 `yaml:"minItems,omitempty"`
	UniqueItems bool    `yaml:"uniqueItems,omitempty"`

	// Object validation
	MaxProperties *uint64 `yaml:"maxProperties,omitempty"`
	MinProperties *uint64 `yaml:"minProperties,omitempty"`

	// Enumeration / fixed
	Enum    []any `yaml:"enum,omitempty"`
	Default any   `yaml:"default,omitempty"`

	// Composition
	OneOf []*Schema `yaml:"oneOf,omitempty"`
	AllOf []*Schema `yaml:"allOf,omitempty"`
	AnyOf []*Schema `yaml:"anyOf,omitempty"`
	Not   *Schema   `yaml:"not,omitempty"`

	// Object
	Properties           map[string]*Schema `yaml:"properties,omitempty"`
	Required             []string           `yaml:"required,omitempty"`
	AdditionalProperties any                `yaml:"additionalProperties,omitempty"`

	// Array
	Items *Schema `yaml:"items,omitempty"`

	// OAS-specific
	Nullable      bool                   `yaml:"nullable,omitempty"`
	Discriminator *Discriminator         `yaml:"discriminator,omitempty"`
	ReadOnly      bool                   `yaml:"readOnly,omitempty"`
	WriteOnly     bool                   `yaml:"writeOnly,omitempty"`
	XML           *XML                   `yaml:"xml,omitempty"`
	ExternalDocs  *ExternalDocumentation `yaml:"externalDocs,omitempty"`
	Example       any                    `yaml:"example,omitempty"`
	Deprecated    bool                   `yaml:"deprecated,omitempty"`

	Extensions map[string]any `yaml:",inline,omitempty"`
}

Schema is the OpenAPI 3.0.3 Schema Object — a strict subset of JSON Schema Draft 4 with OAS-specific keywords (nullable, discriminator, readOnly/writeOnly, xml, externalDocs, example).

type SecurityProvider

type SecurityProvider interface {
	SecurityRequirement() (scheme string, scopes []string)
}

SecurityProvider lets an acceptance declare that it enforces a particular security scheme for every HTTP method on every guarded route. Each acceptance can return one scheme + scopes. Builder aggregates these along the inherited acceptance chain.

Acceptances that conditionally skip enforcement on certain HTTP methods (e.g. CSRF acceptances that exempt GET) should implement MethodAwareSecurityProvider instead — the walker prefers it when present so the OpenAPI security requirement matches actual runtime enforcement per method.

type SecurityRef

type SecurityRef struct {
	Scheme string
	Scopes []string
}

SecurityRef binds an operation to a named security scheme with optional scopes (used by OAuth2-style schemes).

type SecurityScheme

type SecurityScheme struct {
	Type             string         `yaml:"type"`
	Description      string         `yaml:"description,omitempty"`
	Name             string         `yaml:"name,omitempty"`
	In               string         `yaml:"in,omitempty"`
	Scheme           string         `yaml:"scheme,omitempty"`
	BearerFormat     string         `yaml:"bearerFormat,omitempty"`
	Flows            *OAuthFlows    `yaml:"flows,omitempty"`
	OpenIDConnectURL string         `yaml:"openIdConnectUrl,omitempty"`
	Extensions       map[string]any `yaml:",inline,omitempty"`
}

SecurityScheme is the security scheme object. Supported types per the spec: apiKey, http, oauth2, openIdConnect.

type Selector

type Selector struct {
	Paths    []string `yaml:"paths,omitempty"`
	Packages []string `yaml:"packages,omitempty"`
	Tags     []string `yaml:"tags,omitempty"`
	Profiles []string `yaml:"profiles,omitempty"`
}

Selector chooses operations by path patterns, package paths, OpenAPI tags, or declared profile scopes. All non-empty fields combine with logical AND. A nil/empty Selector matches everything.

Patterns:

  • Paths use simple glob with "*" matching one segment and "**" matching any number of segments.
  • Packages match by literal prefix.
  • Tags match the OpenAPI operation tags declared via Spec WithTag / handler-level SpecProvider.GOAISpec / per-method providers, or via goai.Register. This is what the README "tag-based filtering" copy actually refers to.
  • Profiles match the names returned by ProfileScope.GOAIProfileScope (handler or acceptance level). Use this to attach an operation to one of the profile buckets defined in goai.yaml when the operation does not have a corresponding OpenAPI tag.

func (Selector) IsEmpty

func (s Selector) IsEmpty() bool

IsEmpty reports whether the selector has no rules.

func (Selector) Match

func (s Selector) Match(c OperationCandidate, declaredProfiles, operationTags []string) bool

Match reports whether the candidate matches every non-empty field on the selector. declaredProfiles is the union returned by ProfileScope across the handler and its acceptance chain; operationTags is the union of OpenAPI tags assigned to this operation by Spec / Register / classifier.

type Server

type Server struct {
	URL         string                     `yaml:"url"`
	Description string                     `yaml:"description,omitempty"`
	Variables   map[string]*ServerVariable `yaml:"variables,omitempty"`
	Extensions  map[string]any             `yaml:",inline,omitempty"`
}

Server represents one entry in the top-level servers array, or in PathItem.Servers / Operation.Servers when overriding the global value.

type ServerVariable

type ServerVariable struct {
	Enum        []string       `yaml:"enum,omitempty"`
	Default     string         `yaml:"default"`
	Description string         `yaml:"description,omitempty"`
	Extensions  map[string]any `yaml:",inline,omitempty"`
}

ServerVariable describes one URL template variable for a Server URL. Default is required by the spec; Enum constrains the allowed values.

type Spec

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

Spec carries optional metadata for a single (handler, method) operation. All fields are deliberately unexported; callers shape Spec values through functional options exclusively. This keeps the public API forward compatible — new fields can be added without breaking call sites.

func NewSpec

func NewSpec(opts ...Option) Spec

NewSpec returns a Spec built from the supplied options. Useful when an optional SpecProvider.GOAISpec returns Spec values.

func (Spec) Callbacks

func (s Spec) Callbacks() map[string]Callback

Callbacks returns the operation's callbacks map (callers must not mutate).

func (Spec) Deprecated

func (s Spec) Deprecated() bool

Deprecated reports whether the operation is deprecated.

func (Spec) Description

func (s Spec) Description() string

Description reports the spec description.

func (Spec) Examples

func (s Spec) Examples() map[string]any

Examples returns the example map (callers must not mutate).

func (Spec) ExternalDocs

func (s Spec) ExternalDocs() *ExternalDocumentation

ExternalDocs returns the operation-level external docs reference, or nil.

func (Spec) ExtraHeaders

func (s Spec) ExtraHeaders() []HeaderDef

ExtraHeaders returns extra headers configured for the operation.

func (Spec) ExtraParams

func (s Spec) ExtraParams() []PathParam

ExtraParams returns extra parameters configured for the operation.

func (Spec) MultiExamples

func (s Spec) MultiExamples() map[string]map[string]*Example

MultiExamples returns the per-media-type, per-name Example map. The returned map and its inner buckets must not be mutated by callers.

func (Spec) OperationID

func (s Spec) OperationID() string

OperationID reports the explicit operation id, or empty if unset.

func (Spec) OperationServers

func (s Spec) OperationServers() []Server

OperationServers returns the operation-level servers, if any.

func (Spec) RequestMediaType

func (s Spec) RequestMediaType() string

RequestMediaType returns the operator-overridden request media type, or empty string when goai should fall back to "application/json".

func (Spec) Security

func (s Spec) Security() []SecurityRef

Security returns the configured security refs.

func (Spec) Summary

func (s Spec) Summary() string

Summary reports the spec summary, suitable for builder consumption.

func (Spec) Tags

func (s Spec) Tags() []string

Tags returns a copy of the tag slice.

type SpecProvider

type SpecProvider interface {
	GOAISpec() Spec
}

SpecProvider supplies a Spec that applies to every HTTP method the handler implements. Use this when the spec metadata (tags, security, deprecated) is the same for all methods on the handler, or when the handler only implements one HTTP method — the most common case. The GOAI prefix on the method name keeps it distinct from the handler's HTTP method functions (Get, Post, ...) on the same struct, and the method name mirrors the returned Spec type.

For handlers that need different specs per HTTP method, implement one of the per-method providers below (IndexSpecProvider, GetSpecProvider, ...). The per-method providers override SpecProvider when both are implemented.

type Tag

type Tag struct {
	Name         string                 `yaml:"name"`
	Description  string                 `yaml:"description,omitempty"`
	ExternalDocs *ExternalDocumentation `yaml:"externalDocs,omitempty"`
	Extensions   map[string]any         `yaml:",inline,omitempty"`
}

Tag is a top-level tag definition.

type TraceSpecProvider

type TraceSpecProvider interface {
	GOAITraceSpec() Spec
}

TraceSpecProvider supplies the Spec for the handler's Trace() method (HTTP TRACE).

type XML

type XML struct {
	Name       string         `yaml:"name,omitempty"`
	Namespace  string         `yaml:"namespace,omitempty"`
	Prefix     string         `yaml:"prefix,omitempty"`
	Attribute  bool           `yaml:"attribute,omitempty"`
	Wrapped    bool           `yaml:"wrapped,omitempty"`
	Extensions map[string]any `yaml:",inline,omitempty"`
}

XML adds XML serialization metadata to a Schema.

Directories

Path Synopsis
cmd
goai command
Command goai is the OpenAPI generation CLI for gone-framework projects.
Command goai is the OpenAPI generation CLI for gone-framework projects.
examples
customschema command
Customschema example: register Go types with goai.Register so that the generated spec carries fully fleshed-out request and response schemas, then drive validation constraints via the `goai:"..."` struct tag.
Customschema example: register Go types with goai.Register so that the generated spec carries fully fleshed-out request and response schemas, then drive validation constraints via the `goai:"..."` struct tag.
full command
Full example: drive RunCLI with the complete RunOptions surface so the generated YAML exercises every Document-level OpenAPI 3.0.3 feature goai supports — License, Contact, TermsOfService, ExternalDocs, multiple Servers with ServerVariables, multiple Tags, GlobalSecurity, SecuritySchemes, and a custom DefaultOutput path.
Full example: drive RunCLI with the complete RunOptions surface so the generated YAML exercises every Document-level OpenAPI 3.0.3 feature goai supports — License, Contact, TermsOfService, ExternalDocs, multiple Servers with ServerVariables, multiple Tags, GlobalSecurity, SecuritySchemes, and a custom DefaultOutput path.
merge command
Merge example: walk a route tree, generate a fresh spec, then layer it onto a hand-tuned `openapi.yaml` baseline using goai.Merge3Way.
Merge example: walk a route tree, generate a fresh spec, then layer it onto a hand-tuned `openapi.yaml` baseline using goai.Merge3Way.
quickstart command
Quickstart example: build a minimal route tree, walk it, and print the generated OpenAPI 3.0.3 YAML to stdout.
Quickstart example: build a minimal route tree, walk it, and print the generated OpenAPI 3.0.3 YAML to stdout.
runtime command
Runtime example: serve the generated OpenAPI YAML at /openapi.yaml from inside a running gone HTTP server, instead of generating it ahead of time from a CLI.
Runtime example: serve the generated OpenAPI YAML at /openapi.yaml from inside a running gone HTTP server, instead of generating it ahead of time from a CLI.

Jump to

Keyboard shortcuts

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