Skip to content

hnlbs/apiary

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

8 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

apiary logo

apiary

OpenAPI 3.1 generator for Go — driven by types, not comment soup.

CI Go Report Card Go Reference Release License Go version

OpenAPI 3.1 net/http | gin Zero runtime deps


apiary generates an OpenAPI 3.1 YAML document from annotated Go source code.


Before (swaggo)

// TelegramAuth godoc
// @Summary      Authenticate via Telegram
// @Description  Accepts initData from Telegram WebApp, verifies HMAC signature
// @Tags         auth
// @Accept       json
// @Produce      json
// @Param        body body TelegramAuthRequest true "Request body"
// @Success      200 {object} TelegramAuthResponse
// @Failure      400 {object} ErrorResponse
// @Failure      401 {object} ErrorResponse
// @Router       /api/v1/auth/telegram [post]
func (h *AuthHandler) TelegramAuth(w http.ResponseWriter, r *http.Request) { ... }

After (apiary)

// apiary:operation POST /api/v1/auth/telegram
// summary: Authenticate via Telegram
// description: Accepts initData from Telegram WebApp, verifies HMAC signature
// tags: auth
// security: none
// errors: 400,401,500
func (h *AuthHandler) TelegramAuth(ctx context.Context, req TelegramAuthRequest) (TelegramAuthResponse, error) {
    // business logic — apiary never touches this
}

Installation

go install github.com/honeynil/apiary/cmd/apiary@latest

Usage

# Scan the current module, write to openapi.yaml
apiary ./...

# With JWT security applied globally
apiary -security bearer -title "My API" -version "1.0.0" -out docs/openapi.yaml ./...

# Scan specific package trees
apiary -out docs/openapi.yaml ./internal/handler/... ./internal/dto/...

Flags

Flag Default Description
-out openapi.yaml Output file path. Use - for stdout.
-title API Value of info.title in the spec
-version 0.0.1 Value of info.version in the spec
-description (none) Value of info.description in the spec
-security (none) Global security scheme. Format: bearer, basic, apikey, or myName:bearer for a custom scheme name

Annotation format

Place the marker directly above the function, with no blank lines:

// apiary:operation METHOD /path
// summary: One-line summary
// description: Longer description (may contain colons)
// tags: tag1, tag2
// security: bearer          ← optional, overrides global
// errors: 400,401,403,500

Supported function signatures

All of the following shapes are accepted:

func (h *T) A(ctx context.Context, req MyRequest) (MyResponse, error) // standard
func (h *T) B(req MyRequest) (MyResponse, error)                       // no ctx
func (h *T) C(ctx context.Context) (MyResponse, error)                 // no request body
func (h *T) D() (MyResponse, error)                                    // health-check style
func Handler(c *gin.Context)                                           // gin (see below)

Struct tags

Tag Effect
json:"name" JSON field name. Use "-" to exclude the field.
doc:"text" description in the JSON Schema / parameter
example:"val" example in the JSON Schema / parameter
default:"val" default in the JSON Schema
validate:"required" Marks the field as required
path:"name" Path parameter — matches {name} in the URL
query:"name" Query parameter
header:"name" Header parameter (e.g. X-Currency, Authorization)

Parameter routing

Tag / condition OpenAPI location
path:"name" parameters[in=path] — always required
query:"name" parameters[in=query]
header:"name" parameters[in=header]
Remaining fields on GET/DELETE implicit query parameters
Remaining fields on POST/PUT/PATCH JSON request body

Security

# Define JWT Bearer as the global default
apiary -security bearer ./...

This adds BearerAuth to components/securitySchemes and sets it as the global security requirement. Individual operations can override it:

// apiary:operation POST /api/v1/auth/login
// security: none        <- public, no token required
func (h *AuthHandler) Login(...)

// apiary:operation GET /api/v1/admin/report
// security: bearer      <- explicit (same as global, self-documenting)
func (h *AdminHandler) Report(...)

Built-in scheme names:

Name Type Details
bearer http scheme: bearer, bearerFormat: JWT
basic http scheme: basic
apikey apiKey in: header, name: X-API-Key

Gin support

Apiary recognises func(c *gin.Context) handlers. Because the signature carries no type information, request and response types are specified via annotations:

// apiary:operation POST /api/v1/tasks
// summary: Create task
// tags: tasks
// request: CreateTaskRequest   <- required for gin handlers
// response: TaskDTO            <- required for gin handlers
// errors: 400,401,422,500
func CreateTask(c *gin.Context) {
    var req CreateTaskRequest
    if err := c.ShouldBindJSON(&req); err != nil { ... }
    // ...
}

Slice responses work too:

// apiary:operation GET /api/v1/tasks
// summary: List tasks
// request: ListTasksRequest
// response: []TaskDTO
// errors: 401,500
func ListTasks(c *gin.Context) { ... }

Path, query, and header parameters are still driven by struct tags — the same path:, query:, and header: tags used with standard handlers:

type GetTaskRequest struct {
    ID int64 `path:"id" validate:"required"`
}

See testdata/gin/ for a full task-manager example.


Error responses

errors: 400,401,500 adds a response entry for each code. All error responses share the built-in ErrorResponse schema by default:

ErrorResponse:
  type: object
  properties:
    error:
      type: string
      description: Human-readable error message
  required: [error]

Custom error schemas

Append a type name after the status code to use a different schema for that specific error:

// apiary:operation POST /api/v1/users
// summary: Create user
// errors: 400 ValidationError, 401, 500
func CreateUser(ctx context.Context, req CreateUserRequest) (UserDTO, error) { ... }

type ValidationError struct {
    Message string            `json:"message"`
    Fields  map[string]string `json:"fields" doc:"field name -> error text"`
}

This generates:

  • 400 -> $ref: '#/components/schemas/ValidationError'
  • 401, 500 -> $ref: '#/components/schemas/ErrorResponse' (default)

Mix and match freely — only the codes that need a custom schema require a type name; the rest fall back to ErrorResponse.


Cross-package types

Apiary resolves types from every directory it scans. If a type lives in a different package, include that package in the pattern:

apiary ./internal/handler/... ./internal/dto/...
# or just
apiary ./...

If a type cannot be resolved, apiary prints a warning and emits {type: object} as a placeholder — the YAML is still valid.


Enum support

Apiary automatically detects Go enum patterns — named types with const values — and adds enum to the JSON Schema:

type ProductCategory string

const (
    CategoryElectronics ProductCategory = "electronics"
    CategoryClothing    ProductCategory = "clothing"
    CategoryFood        ProductCategory = "food"
)

type ProductDTO struct {
    Name     string          `json:"name"`
    Category ProductCategory `json:"category" doc:"Product category"`
}

This generates:

category:
    type: string
    description: Product category
    enum:
        - electronics
        - clothing
        - food

No extra annotations needed — just standard Go type + const declarations. Works with string and integer base types.


Examples

Standard handlerstestdata/router/

apiary -security bearer -title "Task Manager API" -version "1.0.0" \
       -out docs/tasks.yaml ./testdata/router

Gin handlerstestdata/gin/

apiary -security bearer -title "Task Manager API (gin)" -version "1.0.0" \
       -out docs/tasks_gin.yaml ./testdata/gin

Or generate all at once:

make generate

Go type -> JSON Schema mapping

Go type JSON Schema
string {type: string}
bool {type: boolean}
int, int32 {type: integer, format: int32}
int64 {type: integer, format: int64}
float32 {type: number, format: float}
float64 {type: number, format: double}
time.Time {type: string, format: date-time}
time.Duration {type: integer, format: int64}
uuid.UUID {type: string, format: uuid}
net.IP {type: string, format: ipv4}
url.URL {type: string, format: uri}
json.RawMessage {} (any)
sql.NullString {type: string}
sql.NullInt64 {type: integer, format: int64}
sql.NullBool {type: boolean}
sql.NullTime {type: string, format: date-time}
[]T {type: array, items: ...}
map[K]V {type: object, additionalProperties: ...}
Struct {$ref: '#/components/schemas/TypeName'}
Embedded struct allOf: [$ref: Base, {own fields}]
interface{} / any {} (any)
Other pkg.Type {type: string} (fallback)

About

Generate OpenAPI 3.1 from Go code

Resources

License

Stars

Watchers

Forks

Contributors