goai

package
v1.15.4 Latest Latest
Warning

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

Go to latest
Published: Apr 27, 2026 License: Apache-2.0 Imports: 29 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
goai emit --root . -o /tmp/x.yaml   # delegates to ./cmd/goaispec
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/docstring/ Handler doc comments with @goai.* OpenAPI directives.
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/docstring
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.MethodAwareSecurityProvider — method-specific variant of SecurityProvider. Returning an empty scheme for one method explicitly suppresses inherited/global security for that operation.
  • goai.PathParamInjector — declare path parameters this acceptance injects into the request (e.g. an OrgScope acceptance that prepends /{organization_id}).
  • goai.PathAwareParamInjector — path-aware variant for acceptances reused under multiple route subtrees.
  • goai.EmissionAwareParamInjector — receives EmissionContext so an injector can distinguish collection vs. item-level operations.
  • goai.ProfileScope — declare which output profiles the operation belongs to (e.g. mgmt-only).
  • goai.ItemIDProvider — let a handler override the generated item placeholder name instead of the default <last-segment>_id.
  • goai.Hidden — drop framework-internal handlers from generated specs.

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.

Doc-comment fallback

Use doc-comment fallback when endpoint metadata belongs beside the handler but should still be source-controlled as OpenAPI. The extractor reads only namespaced @goai.* directives from the handler struct and method Go doc comments; all other comment text remains private implementation documentation and never flows into the generated yaml.

goai.RunCLI(factory, goai.RunOptions{
    OperationDocExtractor: goai.DefaultOperationDocExtractor(),
})

or in goai.yaml:

enableDocstringExtraction: true
docstringBuildTags:
  - api

Directive format:

Directive Shape
@goai.endpoint METHOD /path
@goai.summary summary text
@goai.description description line; repeat to append lines
@goai.operationId operation.id
@goai.tag TagName; repeat for multiple tags
@goai.deprecated no arguments
@goai.param <in> <name> <type> <required|optional> "description" [schemaType=GoStruct] [goType=GoStruct] [key=value...]
@goai.requestBody <required|optional> <mediaType> <type> "description" [schemaType=GoStruct] [goType=GoStruct] [key=value...]
@goai.response <status> "description" [mediaType=...] [type=...] [schemaType=GoStruct] [goType=GoStruct] [schema=...] [example=...]
@goai.header <status> <name> <type> "description" [required] [schemaType=GoStruct] [goType=GoStruct] [key=value...]
@goai.link <status> <name> <operationId|operationRef> "description" [parameters=...] [requestBody=...] [server=...] [x-...]
@goai.example param <in> <name> <exampleName> "summary" <json-value>
@goai.example request <mediaType> <exampleName> "summary" <json-value>
@goai.example response <status> <mediaType> <exampleName> "summary" <json-value>
@goai.example response <status> <mediaType> <exampleName> example={<Example Object>}
@goai.security <scheme> [scope...], or none for explicit no security
@goai.server <url> "description"
@goai.externalDocs <url> "description"
@goai.callback <name> <compact-json-callback-object>
@goai.extension <x-key> <json-value>
@goai.param.description <in> <name> description line; repeat to append lines
@goai.requestBody.description description line; repeat to append lines
@goai.response.description <status> description line; repeat to append lines
@goai.header.description <status> <name> description line; repeat to append lines
@goai.link.description <status> <name> description line; repeat to append lines
@goai.example.description param <in> <name> <exampleName> description line; repeat to append lines
@goai.example.description request <mediaType> <exampleName> description line; repeat to append lines
@goai.example.description response <status> <mediaType> <exampleName> description line; repeat to append lines
@goai.server.description <url> description line; repeat to append lines
@goai.externalDocs.description description line; repeat to append lines
@goai.schemaName Type doc comment only: stable components.schemas key for that struct

For common scalar fields, use directive tokens. For the full OpenAPI object surface, use compact JSON values on schema=..., example=..., @goai.callback, and @goai.extension; JSON may contain quoted strings with spaces, but each structured value must stay on one directive line.

Conventions:

  • Only lines starting with @goai. are parsed.
  • @goai.endpoint documents and validates intent; the walked route method and path remain authoritative.
  • Put shared operation metadata such as tags and security on the handler struct doc comment; put @goai.endpoint, summary, operationId, parameters, request bodies, responses, examples, and per-method overrides on the handler method doc comment.
  • Quote descriptions when they contain spaces.
  • type may be a scalar OpenAPI schema type, array:<itemType>, or a $ref/ref: target. Use schema={...} for full Schema Object fields. Use schemaType=SomeStruct or goType=SomeStruct to reference a Go struct in the same package. Use schemaType=packageAlias.SomeStruct or goType=packageAlias.SomeStruct for an imported package; the alias must match the handler source import name. Type aliases that point to imported structs are resolved to the imported struct component. goai emits resolved structs under components.schemas and uses a $ref. Imported component names include the full import path, so same short names from different packages do not collapse into the wrong schema. Generic structs such as Page[Item] are supported. Explicit schema={...} wins over struct resolution.
  • Structs can choose their public component key. For docstring extraction, put @goai.schemaName PublicName on the struct type doc comment. For reflection-based WithResponseSchema, implement GOAISchemaName() string on the struct type. Custom names are sanitized and still checked for collisions during the build.
  • When docstring schema structs live behind custom Go build tags, pass those tags through docstringBuildTags in goai.yaml or use DefaultOperationDocExtractorWithBuildTags("tag"). The default extractor also honors GOFLAGS=-tags=....
  • x-* options on parameters, responses, headers, and request bodies become Specification Extensions.
  • Explicit Spec values, route-derived path parameters, generated request / response schemas, and acceptance-derived security win. Doc-comment metadata only fills gaps.

Example:

// Google keeps shared OpenAPI metadata for all Google auth operations.
//
// @goai.tag Auth
// @goai.security none
type Google struct { ... }

// Get handles Google OAuth callback implementation details.
// Keep cache, redirect, and retry notes here for maintainers only.
//
// @goai.endpoint GET /api/v1/auth/google
// @goai.summary Handle Google OAuth callback
// @goai.description Verifies the returned state token, exchanges the code with Google,
// @goai.description and redirects the browser back to the application.
// @goai.operationId auth.googleCallback
// @goai.param query code string required "Authorization code returned by Google."
// @goai.param query state string required "CSRF state token returned with the redirect."
// @goai.response 302 "Redirects to the application callback URL."
// @goai.response 400 "Invalid OAuth callback payload." mediaType=application/json schemaType=OAuthError
// @goai.example response 400 application/json invalidState example={"summary":"Invalid state","description":"State token failed validation.","value":{"error":"invalid_state"}}
func (h *Google) Get(...) ghttp.ErrorResponse { ... }

becomes:

/api/v1/auth/google:
  get:
    summary: Handle Google OAuth callback
    description: |
      Verifies the returned state token, exchanges the code with Google,
      and redirects the browser back to the application.
    operationId: auth.googleCallback
    tags:
      - Auth
    parameters:
      - name: code
        in: query
        description: Authorization code returned by Google.
        required: true
        schema:
          type: string
      - name: state
        in: query
        description: CSRF state token returned with the redirect.
        required: true
        schema:
          type: string
    responses:
      "302":
        description: Redirects to the application callback URL.
      "400":
        description: Invalid OAuth callback payload.
    security: []
Per-status responses

Spec carries a responses map for non-success status codes. Use WithResponse for distinct status codes, with optional schema or example:

goai.NewSpec(
    goai.WithSummary("Get user profile"),
    goai.WithSuccessDescription("User profile"),
    goai.WithResponse("400", "Invalid request",
        goai.WithResponseSchema(reflect.TypeOf((*ErrorEnvelope)(nil)))),
    goai.WithResponse("404", "User not found"),
)

The matching success-status entry merges with the operation's auto-generated success response so a registered response Go-type is preserved alongside the spec-supplied description / examples / headers. WithSuccessDescription is a shorthand when the only customisation is the success description.

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.

Config-driven multi-output

For projects that emit more than one OpenAPI document, keep generation policy in goai.yaml and call RunCLIFromConfig from the project-side binary. The route tree is walked once; each configured profile gets its own Build + EmitYAML pass.

func main() {
    goai.RunCLIFromConfig(
        "goai.yaml",
        func() ghttp.RouteEntriesProvider { return handlers.NewAppRoute() },
    )
}

Minimal goai.yaml:

title: My API
version: 1.0.0
baseSpecPath: docs/openapi/openapi.yaml
restrictToBaseSpecPaths: false
excludePaths:
  - /static/**
  - /debug/**
enableDocstringExtraction: true
docstringBuildTags:
  - api

profiles:
  public:
    include:
      paths: ["/api/v1/**"]
      tags: ["Public"]
    exclude:
      paths: ["/api/v1/internal/**"]
  internal:
    include:
      profiles: ["internal"]
  all: {}

output:
  public: docs/openapi/openapi.public.yaml
  internal: docs/openapi/openapi.internal.yaml
  all: docs/openapi/openapi.yaml

Config fields map to the corresponding RunOptions / BuildOptions fields: title, description, version, termsOfService, contact, license, externalDocs, servers, tags, security, securitySchemes, defaultOutput, baseSpecPath, restrictToBaseSpecPaths, excludePaths, profiles, output, enableDocstringExtraction, and docstringBuildTags.

When profiles / output are omitted, the default buckets are public, mgmt, internal, and all, with output files openapi.public.yaml, openapi.mgmt.yaml, openapi.internal.yaml, and openapi.yaml. Selectors can match paths, packages, OpenAPI tags, or declared profiles from ProfileScope.GOAIProfileScope(). Include selectors combine non-empty fields with logical AND; exclude selectors drop an operation when any field matches.

restrictToBaseSpecPaths: true turns a base spec into the endpoint registry: walked routes absent from baseSpecPath are excluded. This is useful when endpoints should not become public documentation until the curated OpenAPI file already lists them. excludePaths is a framework blocklist applied before per-profile filtering.

Three-way merge

goai.Merge3Way(generated, existing, nil) 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.

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.
Config goai.yaml schema used by RunCLIFromConfig.
ConfigProfile On-disk include/exclude selector pair for one profile.
Spec Per-operation metadata produced by Options. Immutable.
Option func(*Spec). Mutates a Spec during NewSpec / Register.
ResponseSpec Per-status response metadata declared through WithResponse.
ResponseOption func(*ResponseSpec). Mutates a ResponseSpec.
Profile Named output bucket with Include/Exclude selectors.
Selector Path / package / tag / profile rule set.
Classifier Path → tag and acceptance-typename → security mapping.
OperationDoc Parsed @goai.* metadata for one handler method.
OperationDocExtractor Hook used by Build/RunCLI for doc-comment fallback.
LintReport Result from LintYAML, including errors and path/operation counts.
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.
WithRequestDescription(s) Sets requestBody.description.
WithSuccessDescription(s) Overrides the conventional success response description.
WithResponse(status, description, opts...) Adds or customizes a response status, including default.
WithResponseSchema(type) Response option: build schema from a Go type.
WithResponseSchemaPrebuilt(schema) Response option: use a prebuilt schema verbatim.
WithResponseMediaType(mt) Response option: override response media type.
WithResponseExample(value) Response option: attach a single inline example.
WithResponseExampleObject(name, ex) Response option: attach a named Example object.
WithResponseHeader(HeaderDef) Response option: attach a response header to that status.
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.

CLI subcommands

The standalone goai binary is a thin project helper plus two pure file utilities:

Command Effect
goai emit --root <dir> [-o <file>] [-args ...] Finds <root>/cmd/goaispec, <root>/cmd/goaiprobe, or <root>/goaispec, then runs go run against the first match. -o becomes the delegate binary's -o; anything after -args is forwarded verbatim.
goai merge --generated <file> --existing <file> [-o <file>] Runs Merge3Way without walking routes. Existing hand-tuned content wins on overlap. -o - writes to stdout.
goai lint <file> Runs LintYAML: yaml parse, OpenAPI 3.x, non-empty info.title/info.version, and at least one valid path mapping.
goai version Prints the bundled package version.
goai help Prints CLI usage.

Examples:

goai emit --root . -o docs/openapi.generated.yaml -args -title "My API"
goai merge --generated docs/openapi.generated.yaml --existing docs/openapi.yaml -o docs/openapi.merged.yaml
goai lint docs/openapi.yaml
goai version
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(archivedHandler, "Get",
    nil, (*ArchivedResponse)(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.


Scope

  • 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, docstring, 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.

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 existing 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 tighter yaml for operations without registered response types.
	SuppressEmptySchemas bool
	// OperationDocExtractor, when non-nil, is consulted as a fallback
	// source for operation-level OpenAPI metadata. Explicit Spec values,
	// route-derived parameters, generated schemas, and acceptance-derived
	// security always win; docstring content fills gaps.
	//
	// Use DefaultOperationDocExtractor() to enable the built-in AST-based
	// implementation. nil disables the fallback.
	OperationDocExtractor OperationDocExtractor
}

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"`

	// EnableDocstringExtraction toggles source-level Go doc-comment
	// extraction for operation-level OpenAPI metadata. When true,
	// RunCLIFromConfig wires DefaultOperationDocExtractorWithBuildTags()
	// into the build pipeline, which reads only namespaced handler
	// `@goai.*` directives. Ordinary Go comments remain source-only.
	// Explicit Spec values, route-derived parameters, generated schemas, and
	// acceptance-derived security continue to win; docstring extraction
	// only fills gaps.
	EnableDocstringExtraction bool `yaml:"enableDocstringExtraction,omitempty"`
	// DocstringBuildTags are passed to DefaultOperationDocExtractorWithBuildTags
	// when EnableDocstringExtraction is true. Use this when handler docs or
	// schema structs live in files guarded by custom Go build tags.
	DocstringBuildTags []string `yaml:"docstringBuildTags,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 OperationDoc added in v1.15.1

type OperationDoc struct {
	Endpoint       OperationEndpoint
	Operation      Operation
	Schemas        map[string]*Schema
	SchemaPackages map[string]string
}

OperationDoc is the doc-comment metadata for one handler method.

type OperationDocExtractor added in v1.15.1

type OperationDocExtractor func(handler any, methodName string) (*OperationDoc, bool)

OperationDocExtractor returns OpenAPI operation metadata extracted from a handler method's source-code doc comment. The default implementation only reads namespaced `@goai.*` directives, keeping surrounding Go comments private to source readers.

func DefaultOperationDocExtractor added in v1.15.1

func DefaultOperationDocExtractor() OperationDocExtractor

DefaultOperationDocExtractor returns the built-in extractor that locates each method's source file via runtime.FuncForPC, parses it with go/ast, and pulls the doc comment off the matching FuncDecl. Parsed files are cached, so repeat lookups are cheap.

Parse strategy:

  • The file path is derived from runtime.FuncForPC; when the binary was built from a different module path or the source is not on disk (e.g. embedded compilation), the extractor returns no operation.
  • Only `@goai.*` directives are public OpenAPI input. Text before, between, or after directives is considered source-only documentation.
  • Complex OpenAPI objects can be supplied as compact JSON values on namespaced directives such as `@goai.callback` and `schema=...`.

The returned extractor is safe for concurrent use.

func DefaultOperationDocExtractorWithBuildTags added in v1.15.1

func DefaultOperationDocExtractorWithBuildTags(tags ...string) OperationDocExtractor

DefaultOperationDocExtractorWithBuildTags returns the built-in docstring extractor using the supplied build tags when scanning sibling files for handler type docs and schema structs. Use this when the goai generator is compiled with custom build tags and schema/helper types live behind the same tags in separate files.

type OperationEndpoint added in v1.15.1

type OperationEndpoint struct {
	Method string
	Path   string
}

OperationEndpoint is the optional endpoint declaration parsed from `@goai.endpoint METHOD /path`. It documents which HTTP operation the comment describes; the route walker remains authoritative.

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 WithRequestDescription added in v1.15.1

func WithRequestDescription(description string) Option

WithRequestDescription sets the operation's request body description. Renders under requestBody.description in the emitted yaml.

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 WithResponse added in v1.15.1

func WithResponse(status string, description string, opts ...ResponseOption) Option

WithResponse declares an operation response under the given HTTP status code. Use multiple times for distinct status codes (e.g. "400", "404", "default"). description should be a short human-readable explanation of what the status code means in the operation's context. Additional content (schema, examples, headers) is layered on via ResponseOption.

Calling WithResponse with the operation's conventional success status (matching successStatus(httpMethod)) updates that response's description in the same way as WithSuccessDescription, but lets the caller also attach a custom schema or examples — useful for handlers that document a response shape distinct from the registered Go response type.

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 WithSuccessDescription added in v1.15.1

func WithSuccessDescription(description string) Option

WithSuccessDescription overrides the method default response description emitted under the conventional success status code (200 / 201 / 204). Use this when the success response merits a richer description than the generic default.

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 ResponseOption added in v1.15.1

type ResponseOption func(*ResponseSpec)

ResponseOption mutates a ResponseSpec built by WithResponse.

func WithResponseExample added in v1.15.1

func WithResponseExample(example any) ResponseOption

WithResponseExample attaches a single inline example to the response content block.

func WithResponseExampleObject added in v1.15.1

func WithResponseExampleObject(name string, example *Example) ResponseOption

WithResponseExampleObject attaches a named Example Object to the response content block. Use multiple times for distinct named examples.

func WithResponseHeader added in v1.15.1

func WithResponseHeader(h HeaderDef) ResponseOption

WithResponseHeader documents a response header attached to this status code's response. Use multiple times for distinct headers.

func WithResponseMediaType added in v1.15.1

func WithResponseMediaType(mediaType string) ResponseOption

WithResponseMediaType overrides the response content media type (default "application/json").

func WithResponseSchema added in v1.15.1

func WithResponseSchema(t reflect.Type) ResponseOption

WithResponseSchema attaches a Go type whose synthesised schema becomes the response body. Use for non-success responses whose body shape is not captured by the registered (handler, method) response type — e.g. an error envelope on a 400 / 404 branch. Pointer types are automatically unwrapped so that *T behaves like T.

func WithResponseSchemaPrebuilt added in v1.15.1

func WithResponseSchemaPrebuilt(schema *Schema) ResponseOption

WithResponseSchemaPrebuilt attaches a pre-built Schema verbatim under the response content block. Use for $ref-based reuse or when the schema is hand-authored.

type ResponseSpec added in v1.15.1

type ResponseSpec struct {
	// Description is rendered verbatim under responses.<status>.description.
	Description string

	// SchemaType, when non-nil, drives schema synthesis from a Go type
	// (the same path used for request bodies and successful response
	// bodies registered via goai.Register). Mutually exclusive with
	// Schema; Schema wins when both are set.
	SchemaType reflect.Type

	// Schema, when non-nil, is used directly without further synthesis.
	// Use this for hand-tuned schemas, $ref-based reuse, or to attach
	// schemas the schema builder cannot derive (e.g. raw JSON Schema
	// objects).
	Schema *Schema

	// MediaType overrides the response content media type. Empty falls
	// back to "application/json" when Schema or SchemaType is set; when
	// both Schema and SchemaType are nil the response carries no
	// content block regardless of MediaType.
	MediaType string

	// Example is a single inline example. Use Examples when richer
	// metadata (named summaries / descriptions / external values) is
	// needed.
	Example any

	// Examples is the per-name Example map. Mutually compatible with
	// Example — yaml encoders emit both fields.
	Examples map[string]*Example

	// Headers documents response headers attached to this status code.
	Headers []HeaderDef
}

ResponseSpec describes a single status-code response declared on an operation via WithResponse. All fields are optional; Description is the only one builder treats as required by OpenAPI semantics. When empty, success responses use the method default, recognised HTTP status codes use net/http.StatusText, "default" uses "Default response", and unknown status keys fall back to "OK" so the emitted yaml stays valid.

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 for handlers that do not implement SpecProvider or SecurityProvider.

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 absent from 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 absent from 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

	// OperationDocExtractor enables source-level Go doc-comment extraction
	// for operation-level OpenAPI metadata. The built-in extractor only
	// reads namespaced `@goai.*` directives from handler struct and method
	// docs, so ordinary implementation comments stay private. Use
	// DefaultOperationDocExtractor() or
	// DefaultOperationDocExtractorWithBuildTags() to turn on the AST-based
	// implementation; pass nil (the default) to disable docstring
	// fallback entirely.
	//
	// Explicit Spec values, route-derived parameters, generated schemas,
	// and acceptance-derived security always win; docstring content only
	// fills gaps.
	OperationDocExtractor OperationDocExtractor

	// 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 SchemaNameProvider added in v1.15.1

type SchemaNameProvider interface {
	GOAISchemaName() string
}

SchemaNameProvider lets a struct type choose its OpenAPI component schema name when goai builds schemas from reflection.

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) RequestDescription added in v1.15.1

func (s Spec) RequestDescription() string

RequestDescription returns the operation request body description, or empty string when unset.

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) Responses added in v1.15.1

func (s Spec) Responses() map[string]*ResponseSpec

Responses returns the per-status response specs declared via WithResponse. The returned map and its entries must not be mutated by callers.

func (Spec) Security

func (s Spec) Security() []SecurityRef

Security returns the configured security refs.

func (Spec) SuccessDescription added in v1.15.1

func (s Spec) SuccessDescription() string

SuccessDescription returns the override for the success response description, or empty string when the builder should use its default.

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.
docstring command
Docstring example: generate OpenAPI metadata from namespaced handler doc-comment directives instead of goai.Register calls.
Docstring example: generate OpenAPI metadata from namespaced handler doc-comment directives instead of goai.Register calls.
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.
internal

Jump to

Keyboard shortcuts

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