GoSemRoute (gswr)

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
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.
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
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