Introduction
OpenAPI is a standard format for describing HTTP APIs. The corvis-swagger crate generates that document from your controllers, serves it as JSON and YAML and mounts two interfaces to explore it: Swagger UI and the Scalar reference.
The information comes from three sources:
- the routes: methods, paths, versions and parameters (
#[param],#[query],#[body],#[headers]); - the parameter types: DTOs with a
corvis::zschema (zod_dto!) or with#[derive(ApiSchema)]; - the
#[api_*]attributes you add to controllers and handlers: tags, descriptions, responses, security.
Installation
Add the crate to your project (it is not on crates.io yet, so a local path is used):
[dependencies]
corvis = { path = "../corvis/crates/corvis" }
corvis-swagger = { path = "../corvis/crates/corvis-swagger" }The #[api_*] attributes are corvis-swagger macros; to make them available, import the crate in every file that uses them:
use corvis_swagger::*;Bootstrap
After creating the application, build the document and mount it:
use corvis::prelude::*;
use corvis_swagger::*;
#[corvis::main]
async fn main() -> corvis::Result<()> {
let mut app = CorvisFactory::create::<AppModule>().await?;
app.set_global_prefix("api");
let config = DocumentBuilder::new()
.set_title("Cats")
.set_description("The cats API")
.set_version("1.0")
.add_tag("cats", "Operations on cats")
.add_bearer_auth()
.build();
let document = cleanup_open_api_doc(SwaggerModule::create_document(&app, &config));
SwaggerModule::setup("docs", &mut app, document);
app.listen(3000).await
}With the application running:
| Path | Content |
|---|---|
http://localhost:3000/docs | Swagger UI |
http://localhost:3000/docs-json | The document as JSON |
http://localhost:3000/docs-yaml | The document as YAML |
DocumentBuilder defines the fixed part of the document (title, version, servers, security schemes). SwaggerModule::create_document walks the application's controllers and fills in paths and components.schemas. cleanup_open_api_doc converts the schemas of the corvis::z DTOs to the OpenAPI format; if you do not use those DTOs it is not needed, but it does no harm either.
Flat routes
The routes mounted by SwaggerModule::setup are not controller routes: they do not appear in the Mapped {...} logs and do not go through guards, pipes or interceptors. If the documentation must not be public, protect it with a middleware or do not mount it in production.
A documented controller
use corvis::prelude::*;
use corvis::zod_dto;
use corvis_swagger::*;
use serde::{Deserialize, Serialize};
#[derive(Debug, Deserialize, Serialize)]
pub struct CreateCatDto {
pub name: String,
pub lives: i64,
}
zod_dto!(CreateCatDto, z::object! { name: z::string().min(1), lives: z::int().min(1).default(9) });
#[derive(Debug, Deserialize, Serialize)]
pub struct CatDto {
pub id: String,
pub name: String,
}
zod_dto!(CatDto, z::object! { id: z::uuid(), name: z::string() });
#[controller("cats")]
#[api_tags("cats")]
#[api_bearer_auth]
#[api_unauthorized_response(description = "Missing token")]
pub struct CatsController;
#[routes]
impl CatsController {
#[post]
#[api_operation(summary = "Create a cat")]
#[api_created_response(type = CatDto)]
#[api_conflict_response(description = "Duplicated")]
fn create(&self, #[body] dto: CreateCatDto) -> Value {
json!({ "id": "00000000-0000-4000-8000-000000000000", "name": dto.name })
}
#[get]
#[api_ok_response(type = [CatDto])]
fn list(&self) -> Value {
json!([])
}
}This controller is the one in crates/corvis-swagger/tests/swagger_app.rs, which checks the generated document field by field. POST /api/cats ends up like this:
{
"operationId": "CatsController_create",
"parameters": [],
"requestBody": {
"required": true,
"content": { "application/json": { "schema": { "$ref": "#/components/schemas/CreateCatDto" } } }
},
"responses": {
"201": { "description": "", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/CatDto" } } } },
"401": { "description": "Missing token" },
"409": { "description": "Duplicated" }
},
"security": [{ "bearer": [] }],
"summary": "Create a cat",
"tags": ["cats"]
}Note that the attributes go below #[controller] and #[get]/#[post], in the order in which you want the responses to appear.
Scalar reference
Besides Swagger UI you can mount the Scalar reference:
scalar::mount(&mut app, "/reference", ApiReferenceOptions::new().document(&document));mount serves the page at that path and everything under it, for any method. ApiReferenceOptions lets you change the theme (.theme("purple")), the title (.page_title(..)), the CDN (.cdn(..)), a nonce for CSP or any key of Scalar's configuration with .set(key, value). Instead of the embedded document you can point to a URL with .url("/docs-json").
Next steps
- Types and parameters: how DTOs and parameters are described.
- Operations: responses, bodies, content types.
- Security: authentication schemes.
- Decorators: the full list of attributes.
- Other features: global prefix, multiple documents, Swagger UI options.