type StoreProduct struct {
ShopID int64 `json:"shop" validate:"required,positive"`
Name string `json:"name" validate:"required,max=80" doc:"Display name"`
Price float64 `json:"price" validate:"min=0"`
}
r.Group("/api/v1/shops/{shop}", func(g *router.Group) {
g.Use(auth.Required(apiGuard)) // Op.Security only documents the scheme
api.Register(g, "POST /products", api.Op{
Name: "products.store",
Summary: "Create a product",
Tags: []string{"products"},
Security: []string{"bearer"},
}, func(c *web.Ctx, in StoreProduct) (resources.Product, error) {
p := models.Product{ShopID: in.ShopID, Name: in.Name, Price: in.Price}
if err := orm.Create(c.Context(), db, &p); err != nil {
return resources.Product{}, err
}
return resources.FromProduct(p), nil
})
})Input#
- The input is built from the query string, the JSON or form body and the path parameters, in that order; path parameters win over body fields of the same name (a body cannot change
{shop}). GET,HEADandDELETEread only the query string.- It is validated with the input struct's
validatetags (the same rules asweb.Bind); failures answer 422 with field errors, malformed JSON answers 400. - Use
struct{}as the input type when there is none.
Output#
| Case | Status |
|---|---|
Op.Status set | that status |
output type is struct{} | 204, no body |
POST | 201 |
| otherwise | 200 |
Errors returned by the handler go through the normal problem+json path.
Response validation#
Set Op.ValidateResponse: true to check the output against its validate tags before it is written (the output type must be a struct, otherwise Register panics at boot):
api.Register(g, "GET /tasks", api.Op{Name: "tasks.index", ValidateResponse: true}, tasks.Index)The check runs when web.Services.CheckResponses is on (the same switch as web.JSONWith). The app turns it on whenever APP_ENV is not production, that is development or test: a staging deployment that runs with APP_ENV=development checks too and answers 500 on a mismatch. A mismatch is a server bug: it is logged and answered with a generic 500 instead of sending data that breaks the documented contract. In production the output is written without the check.
A list field tagged validate:"required" must be present and not null (a nil slice fails), but may be empty; use min=1 when it needs items.
Route metadata#
Op.Name names the route (and becomes the OpenAPI operationId). Every typed route carries an *api.Operation with its method, full path (group prefixes included), path parameters and input/output types, readable from router.RouteInfo.Doc. That is what the OpenAPI generator uses; plain web.HandlerFunc routes keep working beside typed ones.
Op.Security only declares the security scheme in the OpenAPI document; it does not authenticate anything. Protect the route or its group with auth.Required (and auth.Ability for token abilities).