gswr

module
v0.1.18 Latest Latest
Warning

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

Go to latest
Published: Sep 12, 2026 License: MIT

README

GoSemRoute (gswr)

Golang OpenAPI

Semantic OpenAPI generator for Go projects.

GoSemRoute focuses on semantic recognition, not only annotation parsing.
It walks routing code, follows helper wrappers, and infers request/response schemas from real code paths.

Status

  • Supported:
    • echo v5 / v4
    • gin
  • Planned:
    • fiber
    • chi

This tool is optimized for real internal codebases, but it is still static analysis.
If you hit unsupported patterns, open an issue with a minimal code sample.

Web API reference UI

Use the gswr web command in the project root directory to start an embedded API reference UI.

web

Quick Start

Install CLI:

go install github.com/arsfy/gswr/cmd/gswr@latest

Run:

Generate YAML:

gswr generate
# short alias
gswr g

gswr automatically discovers package main / func main() and, when a project has multiple binaries, selects the only entry that produces API routes. You can also scan another project directory or explicitly resolve ambiguity:

gswr g ../my-service
gswr g --entry ./cmd/control-api/main.go

Generate JSON:

gswr g --out docs/openapi.json

Force format explicitly:

gswr --entry ./main.go --out docs/openapi.out --format json
gswr --entry ./main.go --out docs/openapi.out --format yaml

Open the embedded API reference UI:

gswr web
# scan another project
gswr web ../my-service
# custom bind address and starting port
gswr web --host 0.0.0.0 --port 45000

The command starts at http://127.0.0.1:43877 and opens it in the default browser.

Upgrade a CLI installed with go install:

gswr upgrade

The command checks the latest GitHub Release and installs that concrete version. Locally built or manually downloaded binaries are left untouched and must be updated manually from GitHub Releases.

Why Semantic Recognition

Most generators rely heavily on doc comments.
GoSemRoute additionally infers API shape from code semantics, so it can still produce useful docs with partial or missing annotations.

Core Capabilities

  • Route discovery with nested Group(...) recursion and cross-file router chaining
  • Input inference from Param, QueryParam, QueryParamOr, FormValue, FormValueOr
  • Bind(&req) inference via param/query/header/json tags and required constraints
  • Field visibility via the openapi struct tag (-, readOnly, writeOnly)
  • OpenAPI 3.1 schemas with nullable pointer types, UUID format inference, and base64 []byte encoding
  • Response inference from direct c.JSON(...) returns and helper wrappers like resp.Success(...)
  • Multi-exit response collection (return in different branches)
  • Type inference across nested structs, map literals, and helper argument binding
  • Authentication inference from middleware semantics (bearer, cookie, header apiKey)
  • Tag support via explicit @Tags / @tag and automatic path-based fallback grouping

Annotation Support

  • Operation: @summary / @Summary, @description / @Description, @tag / @tags / @Tags
  • Main metadata: @title, @version, @description, @BasePath / @basepath, @host, @schemes
  • Security overrides: @security, @public (see below)

Security Annotations (@security / @public)

Every authentication heuristic — middleware naming, credential-header detection, session-cookie scans — is a default that explicit annotations override:

// EdgeGate applies edge-node credentials. The name carries no auth keyword
// and the body is opaque; the annotation declares the transports instead.
//
//	@security bearer:api_key, header:X-API-Key
func EdgeGate(next echo.HandlerFunc) echo.HandlerFunc { /* … */ }

// internalStatus is secured by annotation only.
//
//	@security header:X-Internal-Token
func internalStatus(c *echo.Context) error { /* … */ }

// healthz stays public despite enforcing group middleware.
//
//	@public
func healthz(c *echo.Context) error { /* … */ }
  • On a middleware function, @security declares the credential transports that middleware accepts (bearer, bearer:api_key, header:<name>, cookie:<name>), regardless of how the middleware (or its receiver's type) is named and whether its body is analyzable.
  • On a route handler, @security both secures the route and pins its schemes exactly.
  • @public on a handler keeps the route unauthenticated even when group or router middleware would otherwise enforce authentication.

Scheme IDs from a previously generated document (bearer_api_key, x_api_key, cookie_session, header_X_Api_Key, bearerAuth) are accepted as well, so annotations can be copied straight from generated output. Multiple @security lines accumulate; @public wins over @security on the same handler.

Known limits — each is exactly what the annotations are for:

  • Credential type classification (bearer API key vs JWT vs plain header) ultimately rests on package/function naming; a helper in a package named credentials reading Authorization yields header:Authorization, not bearer:api_key.
  • Enforcing detection for guards that only check echo-context principals (RequireActiveUser-style) remains name-based.
  • Cookie names stored in receiver fields (s.cookieName) initialized at a construction site are not traced across functions; the generic cookie:session placeholder (rewritten from the env-var scan) or an annotation is used instead.

Field Visibility (openapi struct tag)

Struct fields can be hidden from or annotated in the generated schema with an openapi struct tag. It is independent of encoding/json, so adding it never changes runtime (de)serialization.

Tag Effect
openapi:"-" Omit the field from request, response and component schemas. Use this for internal config that is exchanged between the control plane and edge nodes but is not part of the public API.
openapi:"readOnly" Emit the field with the native OpenAPI readOnly: true flag (present in responses, ignored on writes).
openapi:"writeOnly" Emit the field with the native OpenAPI writeOnly: true flag (present in requests, ignored on reads).
type WAFPolicy struct {
    Enabled       bool   `json:"enabled"`
    GeoIPDatabase string `json:"geoip_database,omitempty" openapi:"-"` // control-plane internal
}

readOnly/writeOnly are kept as native Schema Object flags instead of stripping the field at the request/response stage: gswr caches one component per package+type, and the same type is typically shared between request and response bodies. Removing the field would corrupt the shared component, whereas readOnly/writeOnly annotate it without conflict.

OpenAPI 3.1 Schema Mapping

Generated documents use OpenAPI 3.1.0 and its JSON Schema vocabulary. Go pointer fields are nullable, []byte is represented as a base64-encoded string, and UUID types from common UUID packages use format: uuid.

Go type OpenAPI 3.1 schema
*string type: [string, "null"]
*MyStruct anyOf: [$ref, {type: "null"}]
[]byte / []uint8 type: string, contentEncoding: base64
uuid.UUID type: string, format: uuid

Example Pattern (Helper Wrappers)

GoSemRoute can infer response schema through helper layers:

func Success(c *echo.Context, data any) error {
  return c.JSON(http.StatusOK, types.Response{Code: "ok", Data: data})
}

func List(c *echo.Context) error {
  id, _ := ParseIDParam(c, "id")
  return Success(c, map[string]any{
    "id": id,
  })
}

Generated 200 schema will include a typed data.id field instead of a generic object.

Example API

package resp is a secondary abstraction layer for input and output handling.

// @summary Edit user
// @description Edits user profile fields with helper-based parsing.
// @Tags user
func edit(c *echo.Context) error {
	id, _ := resp.ParseIDParam(c, "id")
	age := resp.ParseIntForm(c, "age", 18)
	email := c.FormValueOr("email", "default@example.com") // Description 🎉

	if id <= 0 {
		return resp.BadRequest(c, "id <= 0")
	}

	return resp.Success(c, map[string]any{
		"id":  id,
		"age": age,
		"email": []string{
			email,
		},
	}) // Response Description 🎉
}
/api/v1/user/{id}:
    post:
        operationId: edit
        summary: Edit user
        description: Edits user profile fields with helper-based parsing.
        tags:
            - user
        security:
            - header_Authorization: []
        x-middlewares:
            - AuthMiddleware
        parameters:
            - name: id
                in: path
                required: true
                schema:
                type: number
            - name: age
                in: query
                schema:
                type: number
            - name: email
                in: query
                description: "Description 🎉"
                schema:
                type: string
        responses:
            "200":
                description: "Response Description 🎉"
                content:
                    application/json:
                        schema:
                            type: object
                            properties:
                                code:
                                    type: string
                                    enum:
                                        - ok
                                data:
                                    type: object
                                    properties:
                                        age:
                                            type: number
                                        email:
                                            type: array
                                            items:
                                                type: string
                                        id:
                                            type: number
                                    required:
                                        - age
                                        - email
                                        - id
            "400":
                description: Client Error
                content:
                    application/json:
                        schema:
                            type: object
                            properties:
                                code:
                                    type: string
                                    enum:
                                        - id <= 0

Current Limitations

  • Dynamic runtime-only patterns (reflection-heavy dispatch, generated handlers) may not be fully resolved
  • Ambiguous symbols with no import/type context may degrade to generic object schema
  • This is static analysis, not runtime tracing

Development

Run tests:

go test ./...

Rebuild the frontend assets embedded by the CLI:

cd web
pnpm build

Directories

Path Synopsis
cmd
gswr command
internal

Jump to

Keyboard shortcuts

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