--- name: go-swagger3-docs description: >- Documents existing Go HTTP APIs with go-swagger3 godoc @ annotations and struct tags, then generates OpenAPI 3 specs. Use when the user asks to document an API, add Swagger/OpenAPI comments, generate oas.json/yml, or mentions go-swagger3. --- # go-swagger3 — document existing Go APIs Copy this file into a consumer project as `.claude/skills/go-swagger3-docs/SKILL.md`. Annotate existing Go code with go-swagger3 godoc `@` tags and struct tags, then run the CLI. Prefer editing comments and tags over rewriting handlers. ## Install ```bash go install github.com/parvez3019/go-swagger3@latest export PATH="$HOME/go/bin:$PATH" # Docker alternative docker run -t --rm -v $(pwd):/app -w /app parvez3019/go-swagger3:latest \ --module-path . --main-file-path ./cmd/api/main.go --output oas.json --schema-without-pkg ``` ## Workflow 1. Confirm `go.mod` exists (Go modules only). 2. Put **service** annotations on the main/entry file (`@Title` and `@Version` required). 3. Annotate each HTTP handler’s godoc (comments must sit directly above the func). 4. Add `json` + OAS tags on request/response structs; add `@Enum` / `@HeaderParameters` types when needed. 5. Generate (pick the matching layout): ```bash # main next to go.mod go-swagger3 --module-path . --output oas.json --schema-without-pkg # main elsewhere go-swagger3 --module-path . --main-file-path ./cmd/api/main.go --output oas.json --schema-without-pkg # only scan handlers under a path go-swagger3 --module-path . --main-file-path ./cmd/api/main.go --handler-path ./internal/handlers --output oas.json --schema-without-pkg # YAML go-swagger3 --module-path . --main-file-path ./cmd/api/main.go --output oas.json --schema-without-pkg --generate-yaml ``` 6. Open the output, fix gaps, re-run with `--debug` if needed. Use `--strict` when hardening. ### CLI flags | Flag | Purpose | |------|---------| | `--module-path` | Module root to scan | | `--main-file-path` | File with service-level annotations | | `--handler-path` | Optional: only scan handlers under this path | | `--output` | Output path (default `oas.json`) | | `--schema-without-pkg` | Schema names without package prefix | | `--generate-yaml` | Emit YAML (`.json` output becomes `.yml`) | | `--exclude` | Comma-separated dirs to skip | | `--quiet` | Reduce log noise | | `--debug` | Debug logging | | `--strict` | Treat parse warnings as fatal | Also: `go-swagger3 fmt -d ./` formats `@` annotations. ### Framework UI Serve UI via `github.com/parvez3019/go-swagger3/swagger` (net/http) or adapters under `swagger/gin`, `swagger/echo`, `swagger/chi`, `swagger/mux`, `swagger/fiber`, `swagger/hertz`, `swagger/buffalo`, `swagger/flamingo`, `swagger/atreugo`. See `docs/MIGRATION_FROM_SWAG.md` and `examples/`. ### Extra operation annotations - `@Accept` / `@Produce` (MIME aliases: `json`, `xml`, `mpfd`, …) - Per-handler `@Security`, `@deprecated`, `@externalDocs.description` / `.url` - `@Param` attributes: `Enums()`, `default()`, `minimum()`, `Format()`, `style()`, `explode()`, … - Generics: `PaginatedResult[User]`; composition: `JSONResult{data=Order}` ## Patterns ### Service (main file) ```go package main // @Title User API // @Version 1.0 // @Description User and restaurants API // @ContactName API Support // @ContactEmail support@example.com // @TermsOfServiceUrl https://example.com/tos // @Server localhost:8080 Server 1 // @Server localhost:8081 Server 2 // @Security AuthorizationHeader read write // @SecurityScheme AuthorizationHeader http bearer Input your token // @LicenseName MIT // @LicenseURL https://en.wikipedia.org/wiki/MIT_License func main() {} ``` ### Shared headers ```go // Headers shared request headers // @HeaderParameters Headers type Headers struct { Authorization string `json:"Authorization" example:"Bearer " skip:"true"` Version string `json:"Client-Version" description:"Client Version"` Language string `json:"Client-Language" $ref:"LanguageEnum"` Platform string `json:"Client-Platform" example:"android" description:"Available values : android, ios, web"` } // @Enum LanguageEnum type LanguageEnum struct { LanguageEnum string `enum:"en-in,en-id,id,en-mx,es-mx" example:"en-in"` } ``` ### Handler operations Prefer `@Router` and `{object}` / `{array}`. Both `@Route`/`@Router` and `object`/`{object}` work. Case-insensitive. `@Tag` aliases `@Resource`. `@Param` shape: `@Param {name} {in} {goType} {required} "{desc}" ["{example}"]` `in`: `path`, `query`, `form`, `header`, `cookie`, `body`, `file` `required`: `true`, `false`, `required`, `optional` Descriptions and examples must be quoted. #### POST body ```go // @Title Create User // @Description Creates and returns a user // @Header model.Headers // @Param request body model.CreateUserRequest true "Create User Request" // @Success 200 {object} model.CreateUserResponse // @Failure 400 {object} model.ErrorResponse // @OperationId CreateUser // @Resource users // @Router /user [post] func CreateUser() {} ``` #### GET with query + nested filter ```go // @Title Get restaurants list // @Description Returns restaurants by filter // @Header model.Headers // @Param count query int32 false "count of restaurants" // @Param offset query int32 false "offset" "100" // @Param order_by query model.OrderByEnum false "order list" // @Param filter query model.Filter false "In json format" // @Success 200 {object} model.GetRestaurantsResponse // @Failure 500 {object} model.ErrorResponse // @OperationId GetRestaurants // @Router /restaurants [get] func GetRestaurants() {} ``` #### Path params + array response ```go // @Title Get user list of a group // @Description Get users related to a specific group // @Param groupID path int true "Id of a specific group" "120" // @Success 200 {array} model.User "Users JSON" // @Failure 400 {object} model.ErrorResponse "Error JSON" // @Resource users // @Router /api/group/{groupID}/users [get] func GetGroupUsers() {} ``` #### PUT / PATCH / DELETE ```go // @Title Update User // @Param userID path string true "User id" // @Param request body model.UpdateUserRequest true "Update payload" // @Success 200 {object} model.User // @Failure 404 {object} model.ErrorResponse // @OperationId UpdateUser // @Router /user/{userID} [put] func UpdateUser() {} // @Title Delete User // @Param userID path string true "User id" // @Success 204 "No Content" // @Failure 404 {object} model.ErrorResponse // @OperationId DeleteUser // @Router /user/{userID} [delete] func DeleteUser() {} ``` #### File upload, cookie, header param ```go // @Title Upload avatar // @Param userID path string true "User id" // @Param file file ignored true "Avatar image" // @Success 201 {object} model.UploadResponse // @Router /user/{userID}/avatar [post] func UploadAvatar() {} // @Title Session info // @Param session_id cookie string true "Session cookie" // @Param X-Request-Id header string false "Client request id" // @Success 200 {object} model.Session // @Router /session [get] func GetSession() {} ``` #### Status-only / string response / response headers ```go // @Success 200 "live endpoint" // @Router /live [get] func Live() {} // @Success 201 {string} string "created id" // @Router /updates [post] func CreateUpdate() {} // @Success 200 {object} model.TokenResponse "Login successful" // @ResponseHeader 200 Set-Cookie string "Access token cookie" "accessToken=...; Path=/; HttpOnly" // @ResponseHeader 200 X-Request-Id string "Unique request identifier" // @Router /login [post] func Login() {} ``` ### Models, enums, field tags ```go // @Enum OrderByEnum type OrderByEnum struct { OrderByEnum string `enum:"nearest,popular,new,highest-rated" example:"popular"` } type CreateUserRequest struct { FirstName string `json:"first_name" readOnly:"true"` LastName string `json:"last_name" example:"Hassan" description:"Last name"` Age int `json:"age" minimum:"18" exclusiveMinimum:"true" maximum:"256" exclusiveMaximum:"true"` EmailID string `json:"email_id" pattern:"[\\w.]+@[\\w.]"` UserName string `json:"user_name" title:"login"` Password string `json:"password" minLength:"6" maxLength:"200"` Roles []string `json:"roles" writeOnly:"true" nullable:"true" uniqueItems:"true" minItems:"1" maxItems:"100"` Country string `json:"country" $ref:"CountriesEnum"` Version string `json:"version" override-example:"11.0.0"` } type CreateUserResponse struct { UserID string `json:"user_id" example:"u_123"` } type GetRestaurantsResponse struct { Restaurants []Restaurant `json:"restaurants" maxProperties:"100" minProperties:"2" additionalProperties:"true"` } type ErrorResponse struct { Code string `json:"code"` Msg string `json:"msg" skip:"true"` // omitted from schema Secret string `json:"-"` // hidden } // @Enum CountriesEnum type CountriesEnum struct { CountriesEnum string `enum:"india,china,mexico,japan" example:"india"` } ``` Useful tags: `example`, `description`, `title`, `required`, `nullable`, `readOnly`, `writeOnly`, `minimum`, `maximum`, `exclusiveMinimum`, `exclusiveMaximum`, `minLength`, `maxLength`, `pattern`, `minItems`, `maxItems`, `uniqueItems`, `minProperties`, `maxProperties`, `additionalProperties`, `enum`, `$ref`, `override-example`, `skip:"true"`. Hide with `json:"-"` or `go-swagger3:"-"`. ### Security (global only) ```go // HTTP bearer / basic // @SecurityScheme AuthorizationHeader http bearer Input your token // @SecurityScheme BasicAuth http basic Login with admin credentials // API key // @SecurityScheme ApiKeyAuth apiKey header X-MyCustomHeader // OpenID Connect // @SecurityScheme OidcAuth openIdConnect https://example.com/.well-known/openid-configuration // OAuth2 + scopes // @SecurityScheme MyApiAuth oauth2AuthCode /oauth/authorize /oauth/token // @SecurityScope MyApiAuth read_user Read a user // @SecurityScope MyApiAuth write_user Write a user // @Security MyApiAuth read_user write_user ``` Also: `oauth2Implicit`, `oauth2ResourceOwnerCredentials`, `oauth2ClientCredentials`. Security applies to the **entire** service — not per operation. ## End-to-end example (minimal API) ```go // main.go // @Title Demo API // @Version 1.0 // @Server http://localhost:8080 Local func main() {} // handler // @Title List items // @Param limit query int false "page size" "20" // @Success 200 {array} Item // @Failure 500 {object} ErrorResponse // @OperationId ListItems // @Router /items [get] func ListItems() {} type Item struct { ID string `json:"id" example:"1"` Name string `json:"name" example:"Widget"` } type ErrorResponse struct { Message string `json:"message"` } ``` ## Troubleshooting (for agents) Work top-down. After each fix, re-run the CLI. | Symptom | Likely cause | What to do | |---------|--------------|------------| | `command not found: go-swagger3` | Binary not on PATH | `export PATH="$HOME/go/bin:$PATH"` or use Docker | | Empty / missing `info.title` or `info.version` | Service comments not on `--main-file-path` | Move `@Title`/`@Version` to the main file; pass `--main-file-path` explicitly | | No paths in output | Handlers lack `@Router`/`@Route`, or `--handler-path` excludes them | Add `@Router /path [method]`; widen or drop `--handler-path` | | Handler present but operation missing | Comments not on the func godoc, or package outside module | Put `// @...` directly above `func`; keep handlers inside the module | | Schema / type missing or wrong | Wrong package-qualified name, or type not imported/reachable | Use `pkg.Type` as in Go; ensure the type is in-module or a scanned dependency | | Schema names look like `handler.User` | Package prefix included | Pass `--schema-without-pkg` | | `operation ID 'X' is not unique` | Duplicate `@OperationId` | Make each `@OperationId` unique across the module | | Parse warnings / `--strict` fails | Malformed `@Param`/`@Success` (missing quotes, bad `in`, bad status) | Match examples; quote descriptions; valid `in` values only | | Field missing from schema | `skip:"true"`, `json:"-"`, or `go-swagger3:"-"` | Remove skip/hide tags if the field should appear | | Nested / anonymous field wrong | Anonymous embedded structs unsupported | Give the field an explicit named type | | Security missing on one route | Per-operation security not supported | Define `@Security` / `@SecurityScheme` on the service file only | | Unknown tags ignored (e.g. `@Accept`) | Not a go-swagger3 annotation | Use only supported tags listed above | | YAML not produced | Forgot flag or wrong extension expectation | Pass `--generate-yaml` (tool rewrites `.json` → `.yml`) | | Still stuck | Need parser detail | Re-run with `--debug` and fix the first real error | ## Rules - Do not invent unsupported annotations (`@Accept`, swag-only tags, etc.). - Do not claim per-operation security. - Anonymous struct fields are not supported. - Go modules only. - Prefer godoc/tag edits over handler rewrites unless asked. - Keep `@OperationId` values unique.