OpenAPI Generation (rocco)
What is rocco?
rocco is a type-safe HTTP framework. Handlers are generic—Handler[In, Out]—so request and response types are known at compile time. From these types, rocco automatically generates OpenAPI 3.1.0 specifications.
The spec is served at /openapi, with interactive documentation at /docs.
The Problem
API documentation drifts. You update a handler, forget to update the spec, and clients break. Manual spec maintenance doesn't scale.
How Sentinel Enables This
When you create a handler, rocco calls sentinel.Scan on both input and output types:
handler := rocco.NewHandler[CreateUserRequest, User](
"create-user", "POST", "/users",
func(req *rocco.Request[CreateUserRequest]) (User, error) {
// ...
},
)
Internally, rocco extracts metadata for schema generation:
// rocco calls this at handler creation:
sentinel.Scan[CreateUserRequest]()
sentinel.Scan[User]()
Scan (not Inspect) is key—it recursively discovers all related types within your module. If User has a *Profile field, sentinel finds Profile too.
Define types with validation and documentation tags:
type CreateUserRequest struct {
Name string `json:"name" validate:"required,min=3,max=100" description:"User's full name"`
Email string `json:"email" validate:"required,email" description:"Contact email"`
Age int `json:"age,omitempty" validate:"gte=0,lte=150"`
}
Sentinel extracts the metadata:
// metadata.Fields contains:
// - {Name: "Name", Tags: {"json": "name", "validate": "required,min=3,max=100", "description": "User's full name"}}
// - {Name: "Email", Tags: {"json": "email", "validate": "required,email", "description": "Contact email"}}
// - {Name: "Age", Tags: {"json": "age,omitempty", "validate": "gte=0,lte=150"}}
rocco transforms this to OpenAPI schemas:
{
"type": "object",
"required": ["name", "email"],
"properties": {
"name": {
"type": "string",
"minLength": 3,
"maxLength": 100,
"description": "User's full name"
},
"email": {
"type": "string",
"format": "email",
"description": "Contact email"
},
"age": {
"type": "integer",
"minimum": 0,
"maximum": 150
}
}
}
Change the struct, the docs update.
What Sentinel Provides
| rocco needs | Sentinel provides |
|---|---|
| Property names | FieldMetadata.Tags["json"] |
| Property types | FieldMetadata.Type → OpenAPI type mapping |
| Required fields | Tags["validate"] containing required |
| Constraints | Tags["validate"] → OpenAPI constraints |
| Descriptions | Tags["description"] |
| Examples | Tags["example"] |
| Nested schemas | Metadata.Relationships for $ref generation |
Sentinel's recursive scanning means rocco discovers your entire API surface from handler types alone.
Tag Mappings
| Validate Tag | OpenAPI |
|---|---|
required | field in required array |
min=3 | minLength (strings) or minimum (numbers) |
max=100 | maxLength or maximum |
gt=0 | exclusiveMinimum |
lt=100 | exclusiveMaximum |
email | format: "email" |
url | format: "uri" |
uuid | format: "uuid" |
datetime | format: "date-time" |
oneof=a b c | enum: ["a", "b", "c"] |
unique | uniqueItems: true (arrays) |
Nested Types
Sentinel's relationship discovery handles complex types:
type Order struct {
ID string `json:"id"`
Customer *Customer `json:"customer"`
Items []Item `json:"items"`
}
When Order is scanned, sentinel discovers Customer and Item via Metadata.Relationships. rocco uses sentinel.Lookup() to retrieve each type's metadata and includes all three in components.schemas with proper $ref links.
Learn More
- rocco repository
- openapi repository — OpenAPI type definitions